MediaWiki REL1_34
AuthManager.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Auth;
25
26use Config;
29use Psr\Log\LoggerAwareInterface;
30use Psr\Log\LoggerInterface;
31use Status;
32use StatusValue;
33use User;
34use WebRequest;
35use Wikimedia\ObjectFactory;
36
85class AuthManager implements LoggerAwareInterface {
87 const ACTION_LOGIN = 'login';
91 const ACTION_LOGIN_CONTINUE = 'login-continue';
93 const ACTION_CREATE = 'create';
97 const ACTION_CREATE_CONTINUE = 'create-continue';
99 const ACTION_LINK = 'link';
103 const ACTION_LINK_CONTINUE = 'link-continue';
105 const ACTION_CHANGE = 'change';
107 const ACTION_REMOVE = 'remove';
109 const ACTION_UNLINK = 'unlink';
110
112 const SEC_OK = 'ok';
114 const SEC_REAUTH = 'reauth';
116 const SEC_FAIL = 'fail';
117
119 const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
120
122 const AUTOCREATE_SOURCE_MAINT = '::Maintenance::';
123
125 private static $instance = null;
126
128 private $request;
129
131 private $config;
132
134 private $logger;
135
138
141
144
147
150
155 public static function singleton() {
156 if ( self::$instance === null ) {
157 self::$instance = new self(
158 \RequestContext::getMain()->getRequest(),
159 MediaWikiServices::getInstance()->getMainConfig()
160 );
161 }
162 return self::$instance;
163 }
164
170 $this->request = $request;
171 $this->config = $config;
172 $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
173 }
174
178 public function setLogger( LoggerInterface $logger ) {
179 $this->logger = $logger;
180 }
181
185 public function getRequest() {
186 return $this->request;
187 }
188
195 public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
196 $this->logger->warning( "Overriding AuthManager primary authn because $why" );
197
198 if ( $this->primaryAuthenticationProviders !== null ) {
199 $this->logger->warning(
200 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
201 );
202
203 $this->allAuthenticationProviders = array_diff_key(
204 $this->allAuthenticationProviders,
205 $this->primaryAuthenticationProviders
206 );
207 $session = $this->request->getSession();
208 $session->remove( 'AuthManager::authnState' );
209 $session->remove( 'AuthManager::accountCreationState' );
210 $session->remove( 'AuthManager::accountLinkState' );
211 $this->createdAccountAuthenticationRequests = [];
212 }
213
214 $this->primaryAuthenticationProviders = [];
215 foreach ( $providers as $provider ) {
216 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
217 throw new \RuntimeException(
218 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
219 get_class( $provider )
220 );
221 }
222 $provider->setLogger( $this->logger );
223 $provider->setManager( $this );
224 $provider->setConfig( $this->config );
225 $id = $provider->getUniqueId();
226 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
227 throw new \RuntimeException(
228 "Duplicate specifications for id $id (classes " .
229 get_class( $provider ) . ' and ' .
230 get_class( $this->allAuthenticationProviders[$id] ) . ')'
231 );
232 }
233 $this->allAuthenticationProviders[$id] = $provider;
234 $this->primaryAuthenticationProviders[$id] = $provider;
235 }
236 }
237
249 public static function callLegacyAuthPlugin( $method, array $params, $return = null ) {
250 wfDeprecated( __METHOD__, '1.33' );
251 return $return;
252 }
253
267 public function canAuthenticateNow() {
268 return $this->request->getSession()->canSetUser();
269 }
270
289 public function beginAuthentication( array $reqs, $returnToUrl ) {
290 $session = $this->request->getSession();
291 if ( !$session->canSetUser() ) {
292 // Caller should have called canAuthenticateNow()
293 $session->remove( 'AuthManager::authnState' );
294 throw new \LogicException( 'Authentication is not possible now' );
295 }
296
297 $guessUserName = null;
298 foreach ( $reqs as $req ) {
299 $req->returnToUrl = $returnToUrl;
300 // @codeCoverageIgnoreStart
301 if ( $req->username !== null && $req->username !== '' ) {
302 if ( $guessUserName === null ) {
303 $guessUserName = $req->username;
304 } elseif ( $guessUserName !== $req->username ) {
305 $guessUserName = null;
306 break;
307 }
308 }
309 // @codeCoverageIgnoreEnd
310 }
311
312 // Check for special-case login of a just-created account
314 $reqs, CreatedAccountAuthenticationRequest::class
315 );
316 if ( $req ) {
317 if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
318 throw new \LogicException(
319 'CreatedAccountAuthenticationRequests are only valid on ' .
320 'the same AuthManager that created the account'
321 );
322 }
323
324 $user = User::newFromName( $req->username );
325 // @codeCoverageIgnoreStart
326 if ( !$user ) {
327 throw new \UnexpectedValueException(
328 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
329 );
330 } elseif ( $user->getId() != $req->id ) {
331 throw new \UnexpectedValueException(
332 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
333 );
334 }
335 // @codeCoverageIgnoreEnd
336
337 $this->logger->info( 'Logging in {user} after account creation', [
338 'user' => $user->getName(),
339 ] );
340 $ret = AuthenticationResponse::newPass( $user->getName() );
341 $this->setSessionDataForUser( $user );
342 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
343 $session->remove( 'AuthManager::authnState' );
344 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
345 return $ret;
346 }
347
348 $this->removeAuthenticationSessionData( null );
349
350 foreach ( $this->getPreAuthenticationProviders() as $provider ) {
351 $status = $provider->testForAuthentication( $reqs );
352 if ( !$status->isGood() ) {
353 $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
355 Status::wrap( $status )->getMessage()
356 );
357 $this->callMethodOnProviders( 7, 'postAuthentication',
358 [ User::newFromName( $guessUserName ) ?: null, $ret ]
359 );
360 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, null, $guessUserName, [] ] );
361 return $ret;
362 }
363 }
364
365 $state = [
366 'reqs' => $reqs,
367 'returnToUrl' => $returnToUrl,
368 'guessUserName' => $guessUserName,
369 'primary' => null,
370 'primaryResponse' => null,
371 'secondary' => [],
372 'maybeLink' => [],
373 'continueRequests' => [],
374 ];
375
376 // Preserve state from a previous failed login
378 $reqs, CreateFromLoginAuthenticationRequest::class
379 );
380 if ( $req ) {
381 $state['maybeLink'] = $req->maybeLink;
382 }
383
384 $session = $this->request->getSession();
385 $session->setSecret( 'AuthManager::authnState', $state );
386 $session->persist();
387
388 return $this->continueAuthentication( $reqs );
389 }
390
413 public function continueAuthentication( array $reqs ) {
414 $session = $this->request->getSession();
415 try {
416 if ( !$session->canSetUser() ) {
417 // Caller should have called canAuthenticateNow()
418 // @codeCoverageIgnoreStart
419 throw new \LogicException( 'Authentication is not possible now' );
420 // @codeCoverageIgnoreEnd
421 }
422
423 $state = $session->getSecret( 'AuthManager::authnState' );
424 if ( !is_array( $state ) ) {
426 wfMessage( 'authmanager-authn-not-in-progress' )
427 );
428 }
429 $state['continueRequests'] = [];
430
431 $guessUserName = $state['guessUserName'];
432
433 foreach ( $reqs as $req ) {
434 $req->returnToUrl = $state['returnToUrl'];
435 }
436
437 // Step 1: Choose an primary authentication provider, and call it until it succeeds.
438
439 if ( $state['primary'] === null ) {
440 // We haven't picked a PrimaryAuthenticationProvider yet
441 // @codeCoverageIgnoreStart
442 $guessUserName = null;
443 foreach ( $reqs as $req ) {
444 if ( $req->username !== null && $req->username !== '' ) {
445 if ( $guessUserName === null ) {
446 $guessUserName = $req->username;
447 } elseif ( $guessUserName !== $req->username ) {
448 $guessUserName = null;
449 break;
450 }
451 }
452 }
453 $state['guessUserName'] = $guessUserName;
454 // @codeCoverageIgnoreEnd
455 $state['reqs'] = $reqs;
456
457 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
458 $res = $provider->beginPrimaryAuthentication( $reqs );
459 switch ( $res->status ) {
461 $state['primary'] = $id;
462 $state['primaryResponse'] = $res;
463 $this->logger->debug( "Primary login with $id succeeded" );
464 break 2;
466 $this->logger->debug( "Login failed in primary authentication by $id" );
467 if ( $res->createRequest || $state['maybeLink'] ) {
468 $res->createRequest = new CreateFromLoginAuthenticationRequest(
469 $res->createRequest, $state['maybeLink']
470 );
471 }
472 $this->callMethodOnProviders( 7, 'postAuthentication',
473 [ User::newFromName( $guessUserName ) ?: null, $res ]
474 );
475 $session->remove( 'AuthManager::authnState' );
476 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName, [] ] );
477 return $res;
479 // Continue loop
480 break;
483 $this->logger->debug( "Primary login with $id returned $res->status" );
484 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
485 $state['primary'] = $id;
486 $state['continueRequests'] = $res->neededRequests;
487 $session->setSecret( 'AuthManager::authnState', $state );
488 return $res;
489
490 // @codeCoverageIgnoreStart
491 default:
492 throw new \DomainException(
493 get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
494 );
495 // @codeCoverageIgnoreEnd
496 }
497 }
498 if ( $state['primary'] === null ) {
499 $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
501 wfMessage( 'authmanager-authn-no-primary' )
502 );
503 $this->callMethodOnProviders( 7, 'postAuthentication',
504 [ User::newFromName( $guessUserName ) ?: null, $ret ]
505 );
506 $session->remove( 'AuthManager::authnState' );
507 return $ret;
508 }
509 } elseif ( $state['primaryResponse'] === null ) {
510 $provider = $this->getAuthenticationProvider( $state['primary'] );
511 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
512 // Configuration changed? Force them to start over.
513 // @codeCoverageIgnoreStart
515 wfMessage( 'authmanager-authn-not-in-progress' )
516 );
517 $this->callMethodOnProviders( 7, 'postAuthentication',
518 [ User::newFromName( $guessUserName ) ?: null, $ret ]
519 );
520 $session->remove( 'AuthManager::authnState' );
521 return $ret;
522 // @codeCoverageIgnoreEnd
523 }
524 $id = $provider->getUniqueId();
525 $res = $provider->continuePrimaryAuthentication( $reqs );
526 switch ( $res->status ) {
528 $state['primaryResponse'] = $res;
529 $this->logger->debug( "Primary login with $id succeeded" );
530 break;
532 $this->logger->debug( "Login failed in primary authentication by $id" );
533 if ( $res->createRequest || $state['maybeLink'] ) {
534 $res->createRequest = new CreateFromLoginAuthenticationRequest(
535 $res->createRequest, $state['maybeLink']
536 );
537 }
538 $this->callMethodOnProviders( 7, 'postAuthentication',
539 [ User::newFromName( $guessUserName ) ?: null, $res ]
540 );
541 $session->remove( 'AuthManager::authnState' );
542 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName, [] ] );
543 return $res;
546 $this->logger->debug( "Primary login with $id returned $res->status" );
547 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
548 $state['continueRequests'] = $res->neededRequests;
549 $session->setSecret( 'AuthManager::authnState', $state );
550 return $res;
551 default:
552 throw new \DomainException(
553 get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
554 );
555 }
556 }
557
558 $res = $state['primaryResponse'];
559 if ( $res->username === null ) {
560 $provider = $this->getAuthenticationProvider( $state['primary'] );
561 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
562 // Configuration changed? Force them to start over.
563 // @codeCoverageIgnoreStart
565 wfMessage( 'authmanager-authn-not-in-progress' )
566 );
567 $this->callMethodOnProviders( 7, 'postAuthentication',
568 [ User::newFromName( $guessUserName ) ?: null, $ret ]
569 );
570 $session->remove( 'AuthManager::authnState' );
571 return $ret;
572 // @codeCoverageIgnoreEnd
573 }
574
575 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
576 $res->linkRequest &&
577 // don't confuse the user with an incorrect message if linking is disabled
578 $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
579 ) {
580 $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
581 $msg = 'authmanager-authn-no-local-user-link';
582 } else {
583 $msg = 'authmanager-authn-no-local-user';
584 }
585 $this->logger->debug(
586 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
587 );
589 $ret->neededRequests = $this->getAuthenticationRequestsInternal(
590 self::ACTION_LOGIN,
591 [],
593 );
594 if ( $res->createRequest || $state['maybeLink'] ) {
595 $ret->createRequest = new CreateFromLoginAuthenticationRequest(
596 $res->createRequest, $state['maybeLink']
597 );
598 $ret->neededRequests[] = $ret->createRequest;
599 }
600 $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
601 $session->setSecret( 'AuthManager::authnState', [
602 'reqs' => [], // Will be filled in later
603 'primary' => null,
604 'primaryResponse' => null,
605 'secondary' => [],
606 'continueRequests' => $ret->neededRequests,
607 ] + $state );
608 return $ret;
609 }
610
611 // Step 2: Primary authentication succeeded, create the User object
612 // (and add the user locally if necessary)
613
614 $user = User::newFromName( $res->username, 'usable' );
615 if ( !$user ) {
616 $provider = $this->getAuthenticationProvider( $state['primary'] );
617 throw new \DomainException(
618 get_class( $provider ) . " returned an invalid username: {$res->username}"
619 );
620 }
621 if ( $user->getId() === 0 ) {
622 // User doesn't exist locally. Create it.
623 $this->logger->info( 'Auto-creating {user} on login', [
624 'user' => $user->getName(),
625 ] );
626 $status = $this->autoCreateUser( $user, $state['primary'], false );
627 if ( !$status->isGood() ) {
629 Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
630 );
631 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
632 $session->remove( 'AuthManager::authnState' );
633 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
634 return $ret;
635 }
636 }
637
638 // Step 3: Iterate over all the secondary authentication providers.
639
640 $beginReqs = $state['reqs'];
641
642 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
643 if ( !isset( $state['secondary'][$id] ) ) {
644 // This provider isn't started yet, so we pass it the set
645 // of reqs from beginAuthentication instead of whatever
646 // might have been used by a previous provider in line.
647 $func = 'beginSecondaryAuthentication';
648 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
649 } elseif ( !$state['secondary'][$id] ) {
650 $func = 'continueSecondaryAuthentication';
651 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
652 } else {
653 continue;
654 }
655 switch ( $res->status ) {
657 $this->logger->debug( "Secondary login with $id succeeded" );
658 // fall through
660 $state['secondary'][$id] = true;
661 break;
663 $this->logger->debug( "Login failed in secondary authentication by $id" );
664 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
665 $session->remove( 'AuthManager::authnState' );
666 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, $user, $user->getName(), [] ] );
667 return $res;
670 $this->logger->debug( "Secondary login with $id returned " . $res->status );
671 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
672 $state['secondary'][$id] = false;
673 $state['continueRequests'] = $res->neededRequests;
674 $session->setSecret( 'AuthManager::authnState', $state );
675 return $res;
676
677 // @codeCoverageIgnoreStart
678 default:
679 throw new \DomainException(
680 get_class( $provider ) . "::{$func}() returned $res->status"
681 );
682 // @codeCoverageIgnoreEnd
683 }
684 }
685
686 // Step 4: Authentication complete! Set the user in the session and
687 // clean up.
688
689 $this->logger->info( 'Login for {user} succeeded from {clientip}', [
690 'user' => $user->getName(),
691 'clientip' => $this->request->getIP(),
692 ] );
695 $beginReqs, RememberMeAuthenticationRequest::class
696 );
697 $this->setSessionDataForUser( $user, $req && $req->rememberMe );
698 $ret = AuthenticationResponse::newPass( $user->getName() );
699 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
700 $session->remove( 'AuthManager::authnState' );
701 $this->removeAuthenticationSessionData( null );
702 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName(), [] ] );
703 return $ret;
704 } catch ( \Exception $ex ) {
705 $session->remove( 'AuthManager::authnState' );
706 throw $ex;
707 }
708 }
709
721 public function securitySensitiveOperationStatus( $operation ) {
722 $status = self::SEC_OK;
723
724 $this->logger->debug( __METHOD__ . ": Checking $operation" );
725
726 $session = $this->request->getSession();
727 $aId = $session->getUser()->getId();
728 if ( $aId === 0 ) {
729 // User isn't authenticated. DWIM?
731 $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
732 return $status;
733 }
734
735 if ( $session->canSetUser() ) {
736 $id = $session->get( 'AuthManager:lastAuthId' );
737 $last = $session->get( 'AuthManager:lastAuthTimestamp' );
738 if ( $id !== $aId || $last === null ) {
739 $timeSinceLogin = PHP_INT_MAX; // Forever ago
740 } else {
741 $timeSinceLogin = max( 0, time() - $last );
742 }
743
744 $thresholds = $this->config->get( 'ReauthenticateTime' );
745 if ( isset( $thresholds[$operation] ) ) {
746 $threshold = $thresholds[$operation];
747 } elseif ( isset( $thresholds['default'] ) ) {
748 $threshold = $thresholds['default'];
749 } else {
750 throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
751 }
752
753 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
754 $status = self::SEC_REAUTH;
755 }
756 } else {
757 $timeSinceLogin = -1;
758
759 $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
760 if ( isset( $pass[$operation] ) ) {
761 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
762 } elseif ( isset( $pass['default'] ) ) {
763 $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
764 } else {
765 throw new \UnexpectedValueException(
766 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
767 );
768 }
769 }
770
771 \Hooks::run( 'SecuritySensitiveOperationStatus', [
772 &$status, $operation, $session, $timeSinceLogin
773 ] );
774
775 // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
776 if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
777 $status = self::SEC_FAIL;
778 }
779
780 $this->logger->info( __METHOD__ . ": $operation is $status for '{user}'",
781 [
782 'user' => $session->getUser()->getName(),
783 'clientip' => $this->getRequest()->getIP(),
784 ]
785 );
786
787 return $status;
788 }
789
799 public function userCanAuthenticate( $username ) {
800 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
801 if ( $provider->testUserCanAuthenticate( $username ) ) {
802 return true;
803 }
804 }
805 return false;
806 }
807
822 public function normalizeUsername( $username ) {
823 $ret = [];
824 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
825 $normalized = $provider->providerNormalizeUsername( $username );
826 if ( $normalized !== null ) {
827 $ret[$normalized] = true;
828 }
829 }
830 return array_keys( $ret );
831 }
832
847 public function revokeAccessForUser( $username ) {
848 $this->logger->info( 'Revoking access for {user}', [
849 'user' => $username,
850 ] );
851 $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
852 }
853
863 public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
864 $any = false;
865 $providers = $this->getPrimaryAuthenticationProviders() +
867 foreach ( $providers as $provider ) {
868 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
869 if ( !$status->isGood() ) {
870 return Status::wrap( $status );
871 }
872 $any = $any || $status->value !== 'ignored';
873 }
874 if ( !$any ) {
875 $status = Status::newGood( 'ignored' );
876 $status->warning( 'authmanager-change-not-supported' );
877 return $status;
878 }
879 return Status::newGood();
880 }
881
899 public function changeAuthenticationData( AuthenticationRequest $req, $isAddition = false ) {
900 $this->logger->info( 'Changing authentication data for {user} class {what}', [
901 'user' => is_string( $req->username ) ? $req->username : '<no name>',
902 'what' => get_class( $req ),
903 ] );
904
905 $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
906
907 // When the main account's authentication data is changed, invalidate
908 // all BotPasswords too.
909 if ( !$isAddition ) {
910 \BotPassword::invalidateAllPasswordsForUser( $req->username );
911 }
912 }
913
925 public function canCreateAccounts() {
926 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
927 switch ( $provider->accountCreationType() ) {
930 return true;
931 }
932 }
933 return false;
934 }
935
944 public function canCreateAccount( $username, $options = [] ) {
945 // Back compat
946 if ( is_int( $options ) ) {
947 $options = [ 'flags' => $options ];
948 }
949 $options += [
950 'flags' => User::READ_NORMAL,
951 'creating' => false,
952 ];
953 $flags = $options['flags'];
954
955 if ( !$this->canCreateAccounts() ) {
956 return Status::newFatal( 'authmanager-create-disabled' );
957 }
958
959 if ( $this->userExists( $username, $flags ) ) {
960 return Status::newFatal( 'userexists' );
961 }
962
963 $user = User::newFromName( $username, 'creatable' );
964 if ( !is_object( $user ) ) {
965 return Status::newFatal( 'noname' );
966 } else {
967 $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
968 if ( $user->getId() !== 0 ) {
969 return Status::newFatal( 'userexists' );
970 }
971 }
972
973 // Denied by providers?
974 $providers = $this->getPreAuthenticationProviders() +
977 foreach ( $providers as $provider ) {
978 $status = $provider->testUserForCreation( $user, false, $options );
979 if ( !$status->isGood() ) {
980 return Status::wrap( $status );
981 }
982 }
983
984 return Status::newGood();
985 }
986
992 public function checkAccountCreatePermissions( User $creator ) {
993 // Wiki is read-only?
994 if ( wfReadOnly() ) {
995 return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
996 }
997
998 // This is awful, this permission check really shouldn't go through Title.
999 $permErrors = \SpecialPage::getTitleFor( 'CreateAccount' )
1000 ->getUserPermissionsErrors( 'createaccount', $creator, 'secure' );
1001 if ( $permErrors ) {
1002 $status = Status::newGood();
1003 foreach ( $permErrors as $args ) {
1004 $status->fatal( ...$args );
1005 }
1006 return $status;
1007 }
1008
1009 $block = $creator->isBlockedFromCreateAccount();
1010 if ( $block ) {
1011 $errorParams = [
1012 $block->getTarget(),
1013 $block->getReason() ?: wfMessage( 'blockednoreason' )->text(),
1014 $block->getByName()
1015 ];
1016
1017 if ( $block->getType() === DatabaseBlock::TYPE_RANGE ) {
1018 $errorMessage = 'cantcreateaccount-range-text';
1019 $errorParams[] = $this->getRequest()->getIP();
1020 } else {
1021 $errorMessage = 'cantcreateaccount-text';
1022 }
1023
1024 return Status::newFatal( wfMessage( $errorMessage, $errorParams ) );
1025 }
1026
1027 $ip = $this->getRequest()->getIP();
1028 if (
1029 MediaWikiServices::getInstance()->getBlockManager()
1030 ->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ )
1031 ) {
1032 return Status::newFatal( 'sorbs_create_account_reason' );
1033 }
1034
1035 return Status::newGood();
1036 }
1037
1057 public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
1058 $session = $this->request->getSession();
1059 if ( !$this->canCreateAccounts() ) {
1060 // Caller should have called canCreateAccounts()
1061 $session->remove( 'AuthManager::accountCreationState' );
1062 throw new \LogicException( 'Account creation is not possible' );
1063 }
1064
1065 try {
1067 } catch ( \UnexpectedValueException $ex ) {
1068 $username = null;
1069 }
1070 if ( $username === null ) {
1071 $this->logger->debug( __METHOD__ . ': No username provided' );
1072 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1073 }
1074
1075 // Permissions check
1076 $status = $this->checkAccountCreatePermissions( $creator );
1077 if ( !$status->isGood() ) {
1078 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1079 'user' => $username,
1080 'creator' => $creator->getName(),
1081 'reason' => $status->getWikiText( null, null, 'en' )
1082 ] );
1083 return AuthenticationResponse::newFail( $status->getMessage() );
1084 }
1085
1086 $status = $this->canCreateAccount(
1087 $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1088 );
1089 if ( !$status->isGood() ) {
1090 $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1091 'user' => $username,
1092 'creator' => $creator->getName(),
1093 'reason' => $status->getWikiText( null, null, 'en' )
1094 ] );
1095 return AuthenticationResponse::newFail( $status->getMessage() );
1096 }
1097
1098 $user = User::newFromName( $username, 'creatable' );
1099 foreach ( $reqs as $req ) {
1100 $req->username = $username;
1101 $req->returnToUrl = $returnToUrl;
1102 if ( $req instanceof UserDataAuthenticationRequest ) {
1103 $status = $req->populateUser( $user );
1104 if ( !$status->isGood() ) {
1105 $status = Status::wrap( $status );
1106 $session->remove( 'AuthManager::accountCreationState' );
1107 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1108 'user' => $user->getName(),
1109 'creator' => $creator->getName(),
1110 'reason' => $status->getWikiText( null, null, 'en' ),
1111 ] );
1112 return AuthenticationResponse::newFail( $status->getMessage() );
1113 }
1114 }
1115 }
1116
1117 $this->removeAuthenticationSessionData( null );
1118
1119 $state = [
1120 'username' => $username,
1121 'userid' => 0,
1122 'creatorid' => $creator->getId(),
1123 'creatorname' => $creator->getName(),
1124 'reqs' => $reqs,
1125 'returnToUrl' => $returnToUrl,
1126 'primary' => null,
1127 'primaryResponse' => null,
1128 'secondary' => [],
1129 'continueRequests' => [],
1130 'maybeLink' => [],
1131 'ranPreTests' => false,
1132 ];
1133
1134 // Special case: converting a login to an account creation
1136 $reqs, CreateFromLoginAuthenticationRequest::class
1137 );
1138 if ( $req ) {
1139 $state['maybeLink'] = $req->maybeLink;
1140
1141 if ( $req->createRequest ) {
1142 $reqs[] = $req->createRequest;
1143 $state['reqs'][] = $req->createRequest;
1144 }
1145 }
1146
1147 $session->setSecret( 'AuthManager::accountCreationState', $state );
1148 $session->persist();
1149
1150 return $this->continueAccountCreation( $reqs );
1151 }
1152
1158 public function continueAccountCreation( array $reqs ) {
1159 $session = $this->request->getSession();
1160 try {
1161 if ( !$this->canCreateAccounts() ) {
1162 // Caller should have called canCreateAccounts()
1163 $session->remove( 'AuthManager::accountCreationState' );
1164 throw new \LogicException( 'Account creation is not possible' );
1165 }
1166
1167 $state = $session->getSecret( 'AuthManager::accountCreationState' );
1168 if ( !is_array( $state ) ) {
1170 wfMessage( 'authmanager-create-not-in-progress' )
1171 );
1172 }
1173 $state['continueRequests'] = [];
1174
1175 // Step 0: Prepare and validate the input
1176
1177 $user = User::newFromName( $state['username'], 'creatable' );
1178 if ( !is_object( $user ) ) {
1179 $session->remove( 'AuthManager::accountCreationState' );
1180 $this->logger->debug( __METHOD__ . ': Invalid username', [
1181 'user' => $state['username'],
1182 ] );
1183 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1184 }
1185
1186 if ( $state['creatorid'] ) {
1187 $creator = User::newFromId( $state['creatorid'] );
1188 } else {
1189 $creator = new User;
1190 $creator->setName( $state['creatorname'] );
1191 }
1192
1193 // Avoid account creation races on double submissions
1194 $cache = \ObjectCache::getLocalClusterInstance();
1195 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1196 if ( !$lock ) {
1197 // Don't clear AuthManager::accountCreationState for this code
1198 // path because the process that won the race owns it.
1199 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1200 'user' => $user->getName(),
1201 'creator' => $creator->getName(),
1202 ] );
1203 return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1204 }
1205
1206 // Permissions check
1207 $status = $this->checkAccountCreatePermissions( $creator );
1208 if ( !$status->isGood() ) {
1209 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1210 'user' => $user->getName(),
1211 'creator' => $creator->getName(),
1212 'reason' => $status->getWikiText( null, null, 'en' )
1213 ] );
1214 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1215 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1216 $session->remove( 'AuthManager::accountCreationState' );
1217 return $ret;
1218 }
1219
1220 // Load from master for existence check
1221 $user->load( User::READ_LOCKING );
1222
1223 if ( $state['userid'] === 0 ) {
1224 if ( $user->getId() !== 0 ) {
1225 $this->logger->debug( __METHOD__ . ': User exists locally', [
1226 'user' => $user->getName(),
1227 'creator' => $creator->getName(),
1228 ] );
1229 $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1230 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1231 $session->remove( 'AuthManager::accountCreationState' );
1232 return $ret;
1233 }
1234 } else {
1235 if ( $user->getId() === 0 ) {
1236 $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1237 'user' => $user->getName(),
1238 'creator' => $creator->getName(),
1239 'expected_id' => $state['userid'],
1240 ] );
1241 throw new \UnexpectedValueException(
1242 "User \"{$state['username']}\" should exist now, but doesn't!"
1243 );
1244 }
1245 if ( $user->getId() !== $state['userid'] ) {
1246 $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1247 'user' => $user->getName(),
1248 'creator' => $creator->getName(),
1249 'expected_id' => $state['userid'],
1250 'actual_id' => $user->getId(),
1251 ] );
1252 throw new \UnexpectedValueException(
1253 "User \"{$state['username']}\" exists, but " .
1254 "ID {$user->getId()} !== {$state['userid']}!"
1255 );
1256 }
1257 }
1258 foreach ( $state['reqs'] as $req ) {
1259 if ( $req instanceof UserDataAuthenticationRequest ) {
1260 $status = $req->populateUser( $user );
1261 if ( !$status->isGood() ) {
1262 // This should never happen...
1263 $status = Status::wrap( $status );
1264 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1265 'user' => $user->getName(),
1266 'creator' => $creator->getName(),
1267 'reason' => $status->getWikiText( null, null, 'en' ),
1268 ] );
1269 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1270 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1271 $session->remove( 'AuthManager::accountCreationState' );
1272 return $ret;
1273 }
1274 }
1275 }
1276
1277 foreach ( $reqs as $req ) {
1278 $req->returnToUrl = $state['returnToUrl'];
1279 $req->username = $state['username'];
1280 }
1281
1282 // Run pre-creation tests, if we haven't already
1283 if ( !$state['ranPreTests'] ) {
1284 $providers = $this->getPreAuthenticationProviders() +
1287 foreach ( $providers as $id => $provider ) {
1288 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1289 if ( !$status->isGood() ) {
1290 $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1291 'user' => $user->getName(),
1292 'creator' => $creator->getName(),
1293 ] );
1295 Status::wrap( $status )->getMessage()
1296 );
1297 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1298 $session->remove( 'AuthManager::accountCreationState' );
1299 return $ret;
1300 }
1301 }
1302
1303 $state['ranPreTests'] = true;
1304 }
1305
1306 // Step 1: Choose a primary authentication provider and call it until it succeeds.
1307
1308 if ( $state['primary'] === null ) {
1309 // We haven't picked a PrimaryAuthenticationProvider yet
1310 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1311 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1312 continue;
1313 }
1314 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1315 switch ( $res->status ) {
1317 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1318 'user' => $user->getName(),
1319 'creator' => $creator->getName(),
1320 ] );
1321 $state['primary'] = $id;
1322 $state['primaryResponse'] = $res;
1323 break 2;
1325 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1326 'user' => $user->getName(),
1327 'creator' => $creator->getName(),
1328 ] );
1329 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1330 $session->remove( 'AuthManager::accountCreationState' );
1331 return $res;
1333 // Continue loop
1334 break;
1337 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1338 'user' => $user->getName(),
1339 'creator' => $creator->getName(),
1340 ] );
1341 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1342 $state['primary'] = $id;
1343 $state['continueRequests'] = $res->neededRequests;
1344 $session->setSecret( 'AuthManager::accountCreationState', $state );
1345 return $res;
1346
1347 // @codeCoverageIgnoreStart
1348 default:
1349 throw new \DomainException(
1350 get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1351 );
1352 // @codeCoverageIgnoreEnd
1353 }
1354 }
1355 if ( $state['primary'] === null ) {
1356 $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1357 'user' => $user->getName(),
1358 'creator' => $creator->getName(),
1359 ] );
1361 wfMessage( 'authmanager-create-no-primary' )
1362 );
1363 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1364 $session->remove( 'AuthManager::accountCreationState' );
1365 return $ret;
1366 }
1367 } elseif ( $state['primaryResponse'] === null ) {
1368 $provider = $this->getAuthenticationProvider( $state['primary'] );
1369 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1370 // Configuration changed? Force them to start over.
1371 // @codeCoverageIgnoreStart
1373 wfMessage( 'authmanager-create-not-in-progress' )
1374 );
1375 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1376 $session->remove( 'AuthManager::accountCreationState' );
1377 return $ret;
1378 // @codeCoverageIgnoreEnd
1379 }
1380 $id = $provider->getUniqueId();
1381 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1382 switch ( $res->status ) {
1384 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1385 'user' => $user->getName(),
1386 'creator' => $creator->getName(),
1387 ] );
1388 $state['primaryResponse'] = $res;
1389 break;
1391 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1392 'user' => $user->getName(),
1393 'creator' => $creator->getName(),
1394 ] );
1395 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1396 $session->remove( 'AuthManager::accountCreationState' );
1397 return $res;
1400 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1401 'user' => $user->getName(),
1402 'creator' => $creator->getName(),
1403 ] );
1404 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1405 $state['continueRequests'] = $res->neededRequests;
1406 $session->setSecret( 'AuthManager::accountCreationState', $state );
1407 return $res;
1408 default:
1409 throw new \DomainException(
1410 get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1411 );
1412 }
1413 }
1414
1415 // Step 2: Primary authentication succeeded, create the User object
1416 // and add the user locally.
1417
1418 if ( $state['userid'] === 0 ) {
1419 $this->logger->info( 'Creating user {user} during account creation', [
1420 'user' => $user->getName(),
1421 'creator' => $creator->getName(),
1422 ] );
1423 $status = $user->addToDatabase();
1424 if ( !$status->isOK() ) {
1425 // @codeCoverageIgnoreStart
1426 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1427 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1428 $session->remove( 'AuthManager::accountCreationState' );
1429 return $ret;
1430 // @codeCoverageIgnoreEnd
1431 }
1432 $this->setDefaultUserOptions( $user, $creator->isAnon() );
1433 \Hooks::runWithoutAbort( 'LocalUserCreated', [ $user, false ] );
1434 $user->saveSettings();
1435 $state['userid'] = $user->getId();
1436
1437 // Update user count
1438 \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1439
1440 // Watch user's userpage and talk page
1441 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1442
1443 // Inform the provider
1444 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1445
1446 // Log the creation
1447 if ( $this->config->get( 'NewUserLog' ) ) {
1448 $isAnon = $creator->isAnon();
1449 $logEntry = new \ManualLogEntry(
1450 'newusers',
1451 $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1452 );
1453 $logEntry->setPerformer( $isAnon ? $user : $creator );
1454 $logEntry->setTarget( $user->getUserPage() );
1457 $state['reqs'], CreationReasonAuthenticationRequest::class
1458 );
1459 $logEntry->setComment( $req ? $req->reason : '' );
1460 $logEntry->setParameters( [
1461 '4::userid' => $user->getId(),
1462 ] );
1463 $logid = $logEntry->insert();
1464 $logEntry->publish( $logid );
1465 }
1466 }
1467
1468 // Step 3: Iterate over all the secondary authentication providers.
1469
1470 $beginReqs = $state['reqs'];
1471
1472 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1473 if ( !isset( $state['secondary'][$id] ) ) {
1474 // This provider isn't started yet, so we pass it the set
1475 // of reqs from beginAuthentication instead of whatever
1476 // might have been used by a previous provider in line.
1477 $func = 'beginSecondaryAccountCreation';
1478 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1479 } elseif ( !$state['secondary'][$id] ) {
1480 $func = 'continueSecondaryAccountCreation';
1481 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1482 } else {
1483 continue;
1484 }
1485 switch ( $res->status ) {
1487 $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1488 'user' => $user->getName(),
1489 'creator' => $creator->getName(),
1490 ] );
1491 // fall through
1493 $state['secondary'][$id] = true;
1494 break;
1497 $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1498 'user' => $user->getName(),
1499 'creator' => $creator->getName(),
1500 ] );
1501 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1502 $state['secondary'][$id] = false;
1503 $state['continueRequests'] = $res->neededRequests;
1504 $session->setSecret( 'AuthManager::accountCreationState', $state );
1505 return $res;
1507 throw new \DomainException(
1508 get_class( $provider ) . "::{$func}() returned $res->status." .
1509 ' Secondary providers are not allowed to fail account creation, that' .
1510 ' should have been done via testForAccountCreation().'
1511 );
1512 // @codeCoverageIgnoreStart
1513 default:
1514 throw new \DomainException(
1515 get_class( $provider ) . "::{$func}() returned $res->status"
1516 );
1517 // @codeCoverageIgnoreEnd
1518 }
1519 }
1520
1521 $id = $user->getId();
1522 $name = $user->getName();
1523 $req = new CreatedAccountAuthenticationRequest( $id, $name );
1524 $ret = AuthenticationResponse::newPass( $name );
1525 $ret->loginRequest = $req;
1526 $this->createdAccountAuthenticationRequests[] = $req;
1527
1528 $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1529 'user' => $user->getName(),
1530 'creator' => $creator->getName(),
1531 ] );
1532
1533 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1534 $session->remove( 'AuthManager::accountCreationState' );
1535 $this->removeAuthenticationSessionData( null );
1536 return $ret;
1537 } catch ( \Exception $ex ) {
1538 $session->remove( 'AuthManager::accountCreationState' );
1539 throw $ex;
1540 }
1541 }
1542
1560 public function autoCreateUser( User $user, $source, $login = true ) {
1561 if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1562 $source !== self::AUTOCREATE_SOURCE_MAINT &&
1564 ) {
1565 throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1566 }
1567
1568 $username = $user->getName();
1569
1570 // Try the local user from the replica DB
1571 $localId = User::idFromName( $username );
1572 $flags = User::READ_NORMAL;
1573
1574 // Fetch the user ID from the master, so that we don't try to create the user
1575 // when they already exist, due to replication lag
1576 // @codeCoverageIgnoreStart
1577 if (
1578 !$localId &&
1579 MediaWikiServices::getInstance()->getDBLoadBalancer()->getReaderIndex() !== 0
1580 ) {
1581 $localId = User::idFromName( $username, User::READ_LATEST );
1582 $flags = User::READ_LATEST;
1583 }
1584 // @codeCoverageIgnoreEnd
1585
1586 if ( $localId ) {
1587 $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1588 'username' => $username,
1589 ] );
1590 $user->setId( $localId );
1591 $user->loadFromId( $flags );
1592 if ( $login ) {
1593 $this->setSessionDataForUser( $user );
1594 }
1595 $status = Status::newGood();
1596 $status->warning( 'userexists' );
1597 return $status;
1598 }
1599
1600 // Wiki is read-only?
1601 if ( wfReadOnly() ) {
1602 $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
1603 'username' => $username,
1604 'reason' => wfReadOnlyReason(),
1605 ] );
1606 $user->setId( 0 );
1607 $user->loadFromId();
1608 return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
1609 }
1610
1611 // Check the session, if we tried to create this user already there's
1612 // no point in retrying.
1613 $session = $this->request->getSession();
1614 if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1615 $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1616 'username' => $username,
1617 'sessionid' => $session->getId(),
1618 ] );
1619 $user->setId( 0 );
1620 $user->loadFromId();
1621 $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1622 if ( $reason instanceof StatusValue ) {
1623 return Status::wrap( $reason );
1624 } else {
1625 return Status::newFatal( $reason );
1626 }
1627 }
1628
1629 // Is the username creatable?
1630 if ( !User::isCreatableName( $username ) ) {
1631 $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1632 'username' => $username,
1633 ] );
1634 $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
1635 $user->setId( 0 );
1636 $user->loadFromId();
1637 return Status::newFatal( 'noname' );
1638 }
1639
1640 // Is the IP user able to create accounts?
1641 $anon = new User;
1642 if ( $source !== self::AUTOCREATE_SOURCE_MAINT && !MediaWikiServices::getInstance()
1644 ->userHasAnyRight( $anon, 'createaccount', 'autocreateaccount' )
1645 ) {
1646 $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1647 'username' => $username,
1648 'ip' => $anon->getName(),
1649 ] );
1650 $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
1651 $session->persist();
1652 $user->setId( 0 );
1653 $user->loadFromId();
1654 return Status::newFatal( 'authmanager-autocreate-noperm' );
1655 }
1656
1657 // Avoid account creation races on double submissions
1658 $cache = \ObjectCache::getLocalClusterInstance();
1659 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1660 if ( !$lock ) {
1661 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1662 'user' => $username,
1663 ] );
1664 $user->setId( 0 );
1665 $user->loadFromId();
1666 return Status::newFatal( 'usernameinprogress' );
1667 }
1668
1669 // Denied by providers?
1670 $options = [
1671 'flags' => User::READ_LATEST,
1672 'creating' => true,
1673 ];
1674 $providers = $this->getPreAuthenticationProviders() +
1677 foreach ( $providers as $provider ) {
1678 $status = $provider->testUserForCreation( $user, $source, $options );
1679 if ( !$status->isGood() ) {
1680 $ret = Status::wrap( $status );
1681 $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1682 'username' => $username,
1683 'reason' => $ret->getWikiText( null, null, 'en' ),
1684 ] );
1685 $session->set( 'AuthManager::AutoCreateBlacklist', $status );
1686 $user->setId( 0 );
1687 $user->loadFromId();
1688 return $ret;
1689 }
1690 }
1691
1692 $backoffKey = $cache->makeKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1693 if ( $cache->get( $backoffKey ) ) {
1694 $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1695 'username' => $username,
1696 ] );
1697 $user->setId( 0 );
1698 $user->loadFromId();
1699 return Status::newFatal( 'authmanager-autocreate-exception' );
1700 }
1701
1702 // Checks passed, create the user...
1703 $from = $_SERVER['REQUEST_URI'] ?? 'CLI';
1704 $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1705 'username' => $username,
1706 'from' => $from,
1707 ] );
1708
1709 // Ignore warnings about master connections/writes...hard to avoid here
1710 $trxProfiler = \Profiler::instance()->getTransactionProfiler();
1711 $old = $trxProfiler->setSilenced( true );
1712 try {
1713 $status = $user->addToDatabase();
1714 if ( !$status->isOK() ) {
1715 // Double-check for a race condition (T70012). We make use of the fact that when
1716 // addToDatabase fails due to the user already existing, the user object gets loaded.
1717 if ( $user->getId() ) {
1718 $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1719 'username' => $username,
1720 ] );
1721 if ( $login ) {
1722 $this->setSessionDataForUser( $user );
1723 }
1724 $status = Status::newGood();
1725 $status->warning( 'userexists' );
1726 } else {
1727 $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
1728 'username' => $username,
1729 'msg' => $status->getWikiText( null, null, 'en' )
1730 ] );
1731 $user->setId( 0 );
1732 $user->loadFromId();
1733 }
1734 return $status;
1735 }
1736 } catch ( \Exception $ex ) {
1737 $trxProfiler->setSilenced( $old );
1738 $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1739 'username' => $username,
1740 'exception' => $ex,
1741 ] );
1742 // Do not keep throwing errors for a while
1743 $cache->set( $backoffKey, 1, 600 );
1744 // Bubble up error; which should normally trigger DB rollbacks
1745 throw $ex;
1746 }
1747
1748 $this->setDefaultUserOptions( $user, false );
1749
1750 // Inform the providers
1751 $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1752
1753 \Hooks::run( 'LocalUserCreated', [ $user, true ] );
1754 $user->saveSettings();
1755
1756 // Update user count
1757 \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1758 // Watch user's userpage and talk page
1759 \DeferredUpdates::addCallableUpdate( function () use ( $user ) {
1760 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1761 } );
1762
1763 // Log the creation
1764 if ( $this->config->get( 'NewUserLog' ) ) {
1765 $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1766 $logEntry->setPerformer( $user );
1767 $logEntry->setTarget( $user->getUserPage() );
1768 $logEntry->setComment( '' );
1769 $logEntry->setParameters( [
1770 '4::userid' => $user->getId(),
1771 ] );
1772 $logEntry->insert();
1773 }
1774
1775 $trxProfiler->setSilenced( $old );
1776
1777 if ( $login ) {
1778 $this->setSessionDataForUser( $user );
1779 }
1780
1781 return Status::newGood();
1782 }
1783
1795 public function canLinkAccounts() {
1796 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1797 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1798 return true;
1799 }
1800 }
1801 return false;
1802 }
1803
1813 public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1814 $session = $this->request->getSession();
1815 $session->remove( 'AuthManager::accountLinkState' );
1816
1817 if ( !$this->canLinkAccounts() ) {
1818 // Caller should have called canLinkAccounts()
1819 throw new \LogicException( 'Account linking is not possible' );
1820 }
1821
1822 if ( $user->getId() === 0 ) {
1823 if ( !User::isUsableName( $user->getName() ) ) {
1824 $msg = wfMessage( 'noname' );
1825 } else {
1826 $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1827 }
1828 return AuthenticationResponse::newFail( $msg );
1829 }
1830 foreach ( $reqs as $req ) {
1831 $req->username = $user->getName();
1832 $req->returnToUrl = $returnToUrl;
1833 }
1834
1835 $this->removeAuthenticationSessionData( null );
1836
1837 $providers = $this->getPreAuthenticationProviders();
1838 foreach ( $providers as $id => $provider ) {
1839 $status = $provider->testForAccountLink( $user );
1840 if ( !$status->isGood() ) {
1841 $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1842 'user' => $user->getName(),
1843 ] );
1845 Status::wrap( $status )->getMessage()
1846 );
1847 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1848 return $ret;
1849 }
1850 }
1851
1852 $state = [
1853 'username' => $user->getName(),
1854 'userid' => $user->getId(),
1855 'returnToUrl' => $returnToUrl,
1856 'primary' => null,
1857 'continueRequests' => [],
1858 ];
1859
1860 $providers = $this->getPrimaryAuthenticationProviders();
1861 foreach ( $providers as $id => $provider ) {
1862 if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1863 continue;
1864 }
1865
1866 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1867 switch ( $res->status ) {
1869 $this->logger->info( "Account linked to {user} by $id", [
1870 'user' => $user->getName(),
1871 ] );
1872 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1873 return $res;
1874
1876 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1877 'user' => $user->getName(),
1878 ] );
1879 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1880 return $res;
1881
1883 // Continue loop
1884 break;
1885
1888 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1889 'user' => $user->getName(),
1890 ] );
1891 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1892 $state['primary'] = $id;
1893 $state['continueRequests'] = $res->neededRequests;
1894 $session->setSecret( 'AuthManager::accountLinkState', $state );
1895 $session->persist();
1896 return $res;
1897
1898 // @codeCoverageIgnoreStart
1899 default:
1900 throw new \DomainException(
1901 get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1902 );
1903 // @codeCoverageIgnoreEnd
1904 }
1905 }
1906
1907 $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1908 'user' => $user->getName(),
1909 ] );
1911 wfMessage( 'authmanager-link-no-primary' )
1912 );
1913 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1914 return $ret;
1915 }
1916
1922 public function continueAccountLink( array $reqs ) {
1923 $session = $this->request->getSession();
1924 try {
1925 if ( !$this->canLinkAccounts() ) {
1926 // Caller should have called canLinkAccounts()
1927 $session->remove( 'AuthManager::accountLinkState' );
1928 throw new \LogicException( 'Account linking is not possible' );
1929 }
1930
1931 $state = $session->getSecret( 'AuthManager::accountLinkState' );
1932 if ( !is_array( $state ) ) {
1934 wfMessage( 'authmanager-link-not-in-progress' )
1935 );
1936 }
1937 $state['continueRequests'] = [];
1938
1939 // Step 0: Prepare and validate the input
1940
1941 $user = User::newFromName( $state['username'], 'usable' );
1942 if ( !is_object( $user ) ) {
1943 $session->remove( 'AuthManager::accountLinkState' );
1944 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1945 }
1946 if ( $user->getId() !== $state['userid'] ) {
1947 throw new \UnexpectedValueException(
1948 "User \"{$state['username']}\" is valid, but " .
1949 "ID {$user->getId()} !== {$state['userid']}!"
1950 );
1951 }
1952
1953 foreach ( $reqs as $req ) {
1954 $req->username = $state['username'];
1955 $req->returnToUrl = $state['returnToUrl'];
1956 }
1957
1958 // Step 1: Call the primary again until it succeeds
1959
1960 $provider = $this->getAuthenticationProvider( $state['primary'] );
1961 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1962 // Configuration changed? Force them to start over.
1963 // @codeCoverageIgnoreStart
1965 wfMessage( 'authmanager-link-not-in-progress' )
1966 );
1967 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1968 $session->remove( 'AuthManager::accountLinkState' );
1969 return $ret;
1970 // @codeCoverageIgnoreEnd
1971 }
1972 $id = $provider->getUniqueId();
1973 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1974 switch ( $res->status ) {
1976 $this->logger->info( "Account linked to {user} by $id", [
1977 'user' => $user->getName(),
1978 ] );
1979 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1980 $session->remove( 'AuthManager::accountLinkState' );
1981 return $res;
1983 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1984 'user' => $user->getName(),
1985 ] );
1986 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1987 $session->remove( 'AuthManager::accountLinkState' );
1988 return $res;
1991 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1992 'user' => $user->getName(),
1993 ] );
1994 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1995 $state['continueRequests'] = $res->neededRequests;
1996 $session->setSecret( 'AuthManager::accountLinkState', $state );
1997 return $res;
1998 default:
1999 throw new \DomainException(
2000 get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
2001 );
2002 }
2003 } catch ( \Exception $ex ) {
2004 $session->remove( 'AuthManager::accountLinkState' );
2005 throw $ex;
2006 }
2007 }
2008
2034 public function getAuthenticationRequests( $action, User $user = null ) {
2035 $options = [];
2036 $providerAction = $action;
2037
2038 // Figure out which providers to query
2039 switch ( $action ) {
2040 case self::ACTION_LOGIN:
2042 $providers = $this->getPreAuthenticationProviders() +
2045 break;
2046
2048 $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
2049 return is_array( $state ) ? $state['continueRequests'] : [];
2050
2052 $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
2053 return is_array( $state ) ? $state['continueRequests'] : [];
2054
2055 case self::ACTION_LINK:
2056 $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
2057 return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
2058 } );
2059 break;
2060
2062 $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
2063 return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
2064 } );
2065
2066 // To providers, unlink and remove are identical.
2067 $providerAction = self::ACTION_REMOVE;
2068 break;
2069
2071 $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
2072 return is_array( $state ) ? $state['continueRequests'] : [];
2073
2076 $providers = $this->getPrimaryAuthenticationProviders() +
2078 break;
2079
2080 // @codeCoverageIgnoreStart
2081 default:
2082 throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2083 }
2084 // @codeCoverageIgnoreEnd
2085
2086 return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2087 }
2088
2099 $providerAction, array $options, array $providers, User $user = null
2100 ) {
2101 $user = $user ?: \RequestContext::getMain()->getUser();
2102 $options['username'] = $user->isAnon() ? null : $user->getName();
2103
2104 // Query them and merge results
2105 $reqs = [];
2106 foreach ( $providers as $provider ) {
2107 $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2108 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2109 $id = $req->getUniqueId();
2110
2111 // If a required request if from a Primary, mark it as "primary-required" instead
2112 if ( $isPrimary && $req->required ) {
2114 }
2115
2116 if (
2117 !isset( $reqs[$id] )
2118 || $req->required === AuthenticationRequest::REQUIRED
2119 || $reqs[$id] === AuthenticationRequest::OPTIONAL
2120 ) {
2121 $reqs[$id] = $req;
2122 }
2123 }
2124 }
2125
2126 // AuthManager has its own req for some actions
2127 switch ( $providerAction ) {
2128 case self::ACTION_LOGIN:
2129 $reqs[] = new RememberMeAuthenticationRequest;
2130 break;
2131
2133 $reqs[] = new UsernameAuthenticationRequest;
2134 $reqs[] = new UserDataAuthenticationRequest;
2135 if ( $options['username'] !== null ) {
2137 $options['username'] = null; // Don't fill in the username below
2138 }
2139 break;
2140 }
2141
2142 // Fill in reqs data
2143 $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2144
2145 // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2146 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2147 $reqs = array_filter( $reqs, function ( $req ) {
2148 return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2149 } );
2150 }
2151
2152 return array_values( $reqs );
2153 }
2154
2162 private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2163 foreach ( $reqs as $req ) {
2164 if ( !$req->action || $forceAction ) {
2165 $req->action = $action;
2166 }
2167 if ( $req->username === null ) {
2168 $req->username = $username;
2169 }
2170 }
2171 }
2172
2179 public function userExists( $username, $flags = User::READ_NORMAL ) {
2180 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2181 if ( $provider->testUserExists( $username, $flags ) ) {
2182 return true;
2183 }
2184 }
2185
2186 return false;
2187 }
2188
2200 public function allowsPropertyChange( $property ) {
2201 $providers = $this->getPrimaryAuthenticationProviders() +
2203 foreach ( $providers as $provider ) {
2204 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2205 return false;
2206 }
2207 }
2208 return true;
2209 }
2210
2219 public function getAuthenticationProvider( $id ) {
2220 // Fast version
2221 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2222 return $this->allAuthenticationProviders[$id];
2223 }
2224
2225 // Slow version: instantiate each kind and check
2226 $providers = $this->getPrimaryAuthenticationProviders();
2227 if ( isset( $providers[$id] ) ) {
2228 return $providers[$id];
2229 }
2230 $providers = $this->getSecondaryAuthenticationProviders();
2231 if ( isset( $providers[$id] ) ) {
2232 return $providers[$id];
2233 }
2234 $providers = $this->getPreAuthenticationProviders();
2235 if ( isset( $providers[$id] ) ) {
2236 return $providers[$id];
2237 }
2238
2239 return null;
2240 }
2241
2255 public function setAuthenticationSessionData( $key, $data ) {
2256 $session = $this->request->getSession();
2257 $arr = $session->getSecret( 'authData' );
2258 if ( !is_array( $arr ) ) {
2259 $arr = [];
2260 }
2261 $arr[$key] = $data;
2262 $session->setSecret( 'authData', $arr );
2263 }
2264
2272 public function getAuthenticationSessionData( $key, $default = null ) {
2273 $arr = $this->request->getSession()->getSecret( 'authData' );
2274 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2275 return $arr[$key];
2276 } else {
2277 return $default;
2278 }
2279 }
2280
2286 public function removeAuthenticationSessionData( $key ) {
2287 $session = $this->request->getSession();
2288 if ( $key === null ) {
2289 $session->remove( 'authData' );
2290 } else {
2291 $arr = $session->getSecret( 'authData' );
2292 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2293 unset( $arr[$key] );
2294 $session->setSecret( 'authData', $arr );
2295 }
2296 }
2297 }
2298
2305 protected function providerArrayFromSpecs( $class, array $specs ) {
2306 $i = 0;
2307 foreach ( $specs as &$spec ) {
2308 $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2309 }
2310 unset( $spec );
2311 // Sort according to the 'sort' field, and if they are equal, according to 'sort2'
2312 usort( $specs, function ( $a, $b ) {
2313 return $a['sort'] <=> $b['sort']
2314 ?: $a['sort2'] <=> $b['sort2'];
2315 } );
2316
2317 $ret = [];
2318 foreach ( $specs as $spec ) {
2319 $provider = ObjectFactory::getObjectFromSpec( $spec );
2320 if ( !$provider instanceof $class ) {
2321 throw new \RuntimeException(
2322 "Expected instance of $class, got " . get_class( $provider )
2323 );
2324 }
2325 $provider->setLogger( $this->logger );
2326 $provider->setManager( $this );
2327 $provider->setConfig( $this->config );
2328 $id = $provider->getUniqueId();
2329 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2330 throw new \RuntimeException(
2331 "Duplicate specifications for id $id (classes " .
2332 get_class( $provider ) . ' and ' .
2333 get_class( $this->allAuthenticationProviders[$id] ) . ')'
2334 );
2335 }
2336 $this->allAuthenticationProviders[$id] = $provider;
2337 $ret[$id] = $provider;
2338 }
2339 return $ret;
2340 }
2341
2346 private function getConfiguration() {
2347 return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2348 }
2349
2354 protected function getPreAuthenticationProviders() {
2355 if ( $this->preAuthenticationProviders === null ) {
2356 $conf = $this->getConfiguration();
2357 $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2358 PreAuthenticationProvider::class, $conf['preauth']
2359 );
2360 }
2362 }
2363
2369 if ( $this->primaryAuthenticationProviders === null ) {
2370 $conf = $this->getConfiguration();
2371 $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2372 PrimaryAuthenticationProvider::class, $conf['primaryauth']
2373 );
2374 }
2376 }
2377
2383 if ( $this->secondaryAuthenticationProviders === null ) {
2384 $conf = $this->getConfiguration();
2385 $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2386 SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2387 );
2388 }
2390 }
2391
2397 private function setSessionDataForUser( $user, $remember = null ) {
2398 $session = $this->request->getSession();
2399 $delay = $session->delaySave();
2400
2401 $session->resetId();
2402 $session->resetAllTokens();
2403 if ( $session->canSetUser() ) {
2404 $session->setUser( $user );
2405 }
2406 if ( $remember !== null ) {
2407 $session->setRememberUser( $remember );
2408 }
2409 $session->set( 'AuthManager:lastAuthId', $user->getId() );
2410 $session->set( 'AuthManager:lastAuthTimestamp', time() );
2411 $session->persist();
2412
2413 \Wikimedia\ScopedCallback::consume( $delay );
2414
2415 \Hooks::run( 'UserLoggedIn', [ $user ] );
2416 }
2417
2422 private function setDefaultUserOptions( User $user, $useContextLang ) {
2423 $user->setToken();
2424
2425 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2426
2427 $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $contLang;
2428 $user->setOption( 'language', $lang->getPreferredVariant() );
2429
2430 if ( $contLang->hasVariants() ) {
2431 $user->setOption( 'variant', $contLang->getPreferredVariant() );
2432 }
2433 }
2434
2440 private function callMethodOnProviders( $which, $method, array $args ) {
2441 $providers = [];
2442 if ( $which & 1 ) {
2443 $providers += $this->getPreAuthenticationProviders();
2444 }
2445 if ( $which & 2 ) {
2446 $providers += $this->getPrimaryAuthenticationProviders();
2447 }
2448 if ( $which & 4 ) {
2449 $providers += $this->getSecondaryAuthenticationProviders();
2450 }
2451 foreach ( $providers as $provider ) {
2452 $provider->$method( ...$args );
2453 }
2454 }
2455
2460 public static function resetCache() {
2461 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2462 // @codeCoverageIgnoreStart
2463 throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2464 // @codeCoverageIgnoreEnd
2465 }
2466
2467 self::$instance = null;
2468 }
2469
2472}
2473
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.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
if( $line===false) $args
Definition cdb.php:64
This serves as the entry point to the authentication system.
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.
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.
static callLegacyAuthPlugin( $method, array $params, $return=null)
This used to call a legacy AuthPlugin method, if necessary.
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.
__construct(WebRequest $request, Config $config)
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.
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.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
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:40
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition User.php:3768
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2364
addToDatabase()
Add this existing user object to the database.
Definition User.php:4212
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:518
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:1082
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:391
setName( $str)
Set the user name.
Definition User.php:2392
getId()
Get the user's ID.
Definition User.php:2335
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:542
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2355
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:1008
const IGNORE_USER_RIGHTS
Definition User.php:83
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:3109
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4328
getUserPage()
Get this user's personal page title.
Definition User.php:4381
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:871
saveSettings()
Save this user's settings into the database.
Definition User.php:4027
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:2889
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:28
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
$source
A helper class for throttling authentication attempts.
$last
if(!isset( $args[0])) $lang