MediaWiki  master
AuthManager.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
26 use Config;
34 use Psr\Log\LoggerAwareInterface;
35 use Psr\Log\LoggerInterface;
36 use Psr\Log\NullLogger;
37 use ReadOnlyMode;
38 use SpecialPage;
39 use Status;
40 use StatusValue;
41 use User;
42 use WebRequest;
43 use Wikimedia\ObjectFactory;
44 
93 class AuthManager implements LoggerAwareInterface {
95  public const ACTION_LOGIN = 'login';
99  public const ACTION_LOGIN_CONTINUE = 'login-continue';
101  public const ACTION_CREATE = 'create';
105  public const ACTION_CREATE_CONTINUE = 'create-continue';
107  public const ACTION_LINK = 'link';
111  public const ACTION_LINK_CONTINUE = 'link-continue';
113  public const ACTION_CHANGE = 'change';
115  public const ACTION_REMOVE = 'remove';
117  public const ACTION_UNLINK = 'unlink';
118 
120  public const SEC_OK = 'ok';
122  public const SEC_REAUTH = 'reauth';
124  public const SEC_FAIL = 'fail';
125 
127  public const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
128 
130  public const AUTOCREATE_SOURCE_MAINT = '::Maintenance::';
131 
133  private static $instance = null;
134 
136  private $request;
137 
139  private $config;
140 
142  private $objectFactory;
143 
145  private $logger;
146 
148  private $userNameUtils;
149 
152 
155 
158 
161 
164 
166  private $hookContainer;
167 
169  private $hookRunner;
170 
172  private $readOnlyMode;
173 
175  private $blockManager;
176 
179 
185  public static function singleton() {
186  return MediaWikiServices::getInstance()->getAuthManager();
187  }
188 
199  public function __construct(
201  Config $config,
202  ObjectFactory $objectFactory,
208  ) {
209  $this->request = $request;
210  $this->config = $config;
211  $this->objectFactory = $objectFactory;
212  $this->hookContainer = $hookContainer;
213  $this->hookRunner = new HookRunner( $hookContainer );
214  $this->setLogger( new NullLogger() );
215  $this->readOnlyMode = $readOnlyMode;
216  $this->userNameUtils = $userNameUtils;
217  $this->blockManager = $blockManager;
218  $this->blockErrorFormatter = $blockErrorFormatter;
219  }
220 
224  public function setLogger( LoggerInterface $logger ) {
225  $this->logger = $logger;
226  }
227 
231  public function getRequest() {
232  return $this->request;
233  }
234 
241  public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
242  $this->logger->warning( "Overriding AuthManager primary authn because $why" );
243 
244  if ( $this->primaryAuthenticationProviders !== null ) {
245  $this->logger->warning(
246  'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
247  );
248 
249  $this->allAuthenticationProviders = array_diff_key(
250  $this->allAuthenticationProviders,
251  $this->primaryAuthenticationProviders
252  );
253  $session = $this->request->getSession();
254  $session->remove( 'AuthManager::authnState' );
255  $session->remove( 'AuthManager::accountCreationState' );
256  $session->remove( 'AuthManager::accountLinkState' );
257  $this->createdAccountAuthenticationRequests = [];
258  }
259 
260  $this->primaryAuthenticationProviders = [];
261  foreach ( $providers as $provider ) {
262  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
263  throw new \RuntimeException(
264  'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
265  get_class( $provider )
266  );
267  }
268  $provider->setLogger( $this->logger );
269  $provider->setManager( $this );
270  $provider->setConfig( $this->config );
271  $provider->setHookContainer( $this->hookContainer );
272  $id = $provider->getUniqueId();
273  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
274  throw new \RuntimeException(
275  "Duplicate specifications for id $id (classes " .
276  get_class( $provider ) . ' and ' .
277  get_class( $this->allAuthenticationProviders[$id] ) . ')'
278  );
279  }
280  $this->allAuthenticationProviders[$id] = $provider;
281  $this->primaryAuthenticationProviders[$id] = $provider;
282  }
283  }
284 
285  /***************************************************************************/
286  // region Authentication
297  public function canAuthenticateNow() {
298  return $this->request->getSession()->canSetUser();
299  }
300 
319  public function beginAuthentication( array $reqs, $returnToUrl ) {
320  $session = $this->request->getSession();
321  if ( !$session->canSetUser() ) {
322  // Caller should have called canAuthenticateNow()
323  $session->remove( 'AuthManager::authnState' );
324  throw new \LogicException( 'Authentication is not possible now' );
325  }
326 
327  $guessUserName = null;
328  foreach ( $reqs as $req ) {
329  $req->returnToUrl = $returnToUrl;
330  // @codeCoverageIgnoreStart
331  if ( $req->username !== null && $req->username !== '' ) {
332  if ( $guessUserName === null ) {
333  $guessUserName = $req->username;
334  } elseif ( $guessUserName !== $req->username ) {
335  $guessUserName = null;
336  break;
337  }
338  }
339  // @codeCoverageIgnoreEnd
340  }
341 
342  // Check for special-case login of a just-created account
344  $reqs, CreatedAccountAuthenticationRequest::class
345  );
346  if ( $req ) {
347  if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
348  throw new \LogicException(
349  'CreatedAccountAuthenticationRequests are only valid on ' .
350  'the same AuthManager that created the account'
351  );
352  }
353 
354  $user = User::newFromName( $req->username );
355  // @codeCoverageIgnoreStart
356  if ( !$user ) {
357  throw new \UnexpectedValueException(
358  "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
359  );
360  } elseif ( $user->getId() != $req->id ) {
361  throw new \UnexpectedValueException(
362  "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
363  );
364  }
365  // @codeCoverageIgnoreEnd
366 
367  $this->logger->info( 'Logging in {user} after account creation', [
368  'user' => $user->getName(),
369  ] );
370  $ret = AuthenticationResponse::newPass( $user->getName() );
371  $this->setSessionDataForUser( $user );
372  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
373  $session->remove( 'AuthManager::authnState' );
374  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
375  $ret, $user, $user->getName(), [] );
376  return $ret;
377  }
378 
379  $this->removeAuthenticationSessionData( null );
380 
381  foreach ( $this->getPreAuthenticationProviders() as $provider ) {
382  $status = $provider->testForAuthentication( $reqs );
383  if ( !$status->isGood() ) {
384  $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
386  Status::wrap( $status )->getMessage()
387  );
388  $this->callMethodOnProviders( 7, 'postAuthentication',
389  [ User::newFromName( $guessUserName ) ?: null, $ret ]
390  );
391  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit( $ret, null, $guessUserName, [] );
392  return $ret;
393  }
394  }
395 
396  $state = [
397  'reqs' => $reqs,
398  'returnToUrl' => $returnToUrl,
399  'guessUserName' => $guessUserName,
400  'primary' => null,
401  'primaryResponse' => null,
402  'secondary' => [],
403  'maybeLink' => [],
404  'continueRequests' => [],
405  ];
406 
407  // Preserve state from a previous failed login
409  $reqs, CreateFromLoginAuthenticationRequest::class
410  );
411  if ( $req ) {
412  $state['maybeLink'] = $req->maybeLink;
413  }
414 
415  $session = $this->request->getSession();
416  $session->setSecret( 'AuthManager::authnState', $state );
417  $session->persist();
418 
419  return $this->continueAuthentication( $reqs );
420  }
421 
444  public function continueAuthentication( array $reqs ) {
445  $session = $this->request->getSession();
446  try {
447  if ( !$session->canSetUser() ) {
448  // Caller should have called canAuthenticateNow()
449  // @codeCoverageIgnoreStart
450  throw new \LogicException( 'Authentication is not possible now' );
451  // @codeCoverageIgnoreEnd
452  }
453 
454  $state = $session->getSecret( 'AuthManager::authnState' );
455  if ( !is_array( $state ) ) {
457  wfMessage( 'authmanager-authn-not-in-progress' )
458  );
459  }
460  $state['continueRequests'] = [];
461 
462  $guessUserName = $state['guessUserName'];
463 
464  foreach ( $reqs as $req ) {
465  $req->returnToUrl = $state['returnToUrl'];
466  }
467 
468  // Step 1: Choose an primary authentication provider, and call it until it succeeds.
469 
470  if ( $state['primary'] === null ) {
471  // We haven't picked a PrimaryAuthenticationProvider yet
472  // @codeCoverageIgnoreStart
473  $guessUserName = null;
474  foreach ( $reqs as $req ) {
475  if ( $req->username !== null && $req->username !== '' ) {
476  if ( $guessUserName === null ) {
477  $guessUserName = $req->username;
478  } elseif ( $guessUserName !== $req->username ) {
479  $guessUserName = null;
480  break;
481  }
482  }
483  }
484  $state['guessUserName'] = $guessUserName;
485  // @codeCoverageIgnoreEnd
486  $state['reqs'] = $reqs;
487 
488  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
489  $res = $provider->beginPrimaryAuthentication( $reqs );
490  switch ( $res->status ) {
492  $state['primary'] = $id;
493  $state['primaryResponse'] = $res;
494  $this->logger->debug( "Primary login with $id succeeded" );
495  break 2;
497  $this->logger->debug( "Login failed in primary authentication by $id" );
498  if ( $res->createRequest || $state['maybeLink'] ) {
499  $res->createRequest = new CreateFromLoginAuthenticationRequest(
500  $res->createRequest, $state['maybeLink']
501  );
502  }
503  $this->callMethodOnProviders( 7, 'postAuthentication',
504  [ User::newFromName( $guessUserName ) ?: null, $res ]
505  );
506  $session->remove( 'AuthManager::authnState' );
507  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
508  $res, null, $guessUserName, [] );
509  return $res;
511  // Continue loop
512  break;
515  $this->logger->debug( "Primary login with $id returned $res->status" );
516  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
517  $state['primary'] = $id;
518  $state['continueRequests'] = $res->neededRequests;
519  $session->setSecret( 'AuthManager::authnState', $state );
520  return $res;
521 
522  // @codeCoverageIgnoreStart
523  default:
524  throw new \DomainException(
525  get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
526  );
527  // @codeCoverageIgnoreEnd
528  }
529  }
530  if ( $state['primary'] === null ) {
531  $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
533  wfMessage( 'authmanager-authn-no-primary' )
534  );
535  $this->callMethodOnProviders( 7, 'postAuthentication',
536  [ User::newFromName( $guessUserName ) ?: null, $ret ]
537  );
538  $session->remove( 'AuthManager::authnState' );
539  return $ret;
540  }
541  } elseif ( $state['primaryResponse'] === null ) {
542  $provider = $this->getAuthenticationProvider( $state['primary'] );
543  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
544  // Configuration changed? Force them to start over.
545  // @codeCoverageIgnoreStart
547  wfMessage( 'authmanager-authn-not-in-progress' )
548  );
549  $this->callMethodOnProviders( 7, 'postAuthentication',
550  [ User::newFromName( $guessUserName ) ?: null, $ret ]
551  );
552  $session->remove( 'AuthManager::authnState' );
553  return $ret;
554  // @codeCoverageIgnoreEnd
555  }
556  $id = $provider->getUniqueId();
557  $res = $provider->continuePrimaryAuthentication( $reqs );
558  switch ( $res->status ) {
560  $state['primaryResponse'] = $res;
561  $this->logger->debug( "Primary login with $id succeeded" );
562  break;
564  $this->logger->debug( "Login failed in primary authentication by $id" );
565  if ( $res->createRequest || $state['maybeLink'] ) {
566  $res->createRequest = new CreateFromLoginAuthenticationRequest(
567  $res->createRequest, $state['maybeLink']
568  );
569  }
570  $this->callMethodOnProviders( 7, 'postAuthentication',
571  [ User::newFromName( $guessUserName ) ?: null, $res ]
572  );
573  $session->remove( 'AuthManager::authnState' );
574  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
575  $res, null, $guessUserName, [] );
576  return $res;
579  $this->logger->debug( "Primary login with $id returned $res->status" );
580  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
581  $state['continueRequests'] = $res->neededRequests;
582  $session->setSecret( 'AuthManager::authnState', $state );
583  return $res;
584  default:
585  throw new \DomainException(
586  get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
587  );
588  }
589  }
590 
591  $res = $state['primaryResponse'];
592  if ( $res->username === null ) {
593  $provider = $this->getAuthenticationProvider( $state['primary'] );
594  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
595  // Configuration changed? Force them to start over.
596  // @codeCoverageIgnoreStart
598  wfMessage( 'authmanager-authn-not-in-progress' )
599  );
600  $this->callMethodOnProviders( 7, 'postAuthentication',
601  [ User::newFromName( $guessUserName ) ?: null, $ret ]
602  );
603  $session->remove( 'AuthManager::authnState' );
604  return $ret;
605  // @codeCoverageIgnoreEnd
606  }
607 
608  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
609  $res->linkRequest &&
610  // don't confuse the user with an incorrect message if linking is disabled
611  $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
612  ) {
613  $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
614  $msg = 'authmanager-authn-no-local-user-link';
615  } else {
616  $msg = 'authmanager-authn-no-local-user';
617  }
618  $this->logger->debug(
619  "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
620  );
622  $ret->neededRequests = $this->getAuthenticationRequestsInternal(
623  self::ACTION_LOGIN,
624  [],
626  );
627  if ( $res->createRequest || $state['maybeLink'] ) {
628  $ret->createRequest = new CreateFromLoginAuthenticationRequest(
629  $res->createRequest, $state['maybeLink']
630  );
631  $ret->neededRequests[] = $ret->createRequest;
632  }
633  $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
634  $session->setSecret( 'AuthManager::authnState', [
635  'reqs' => [], // Will be filled in later
636  'primary' => null,
637  'primaryResponse' => null,
638  'secondary' => [],
639  'continueRequests' => $ret->neededRequests,
640  ] + $state );
641  return $ret;
642  }
643 
644  // Step 2: Primary authentication succeeded, create the User object
645  // (and add the user locally if necessary)
646 
647  $user = User::newFromName( $res->username, 'usable' );
648  if ( !$user ) {
649  $provider = $this->getAuthenticationProvider( $state['primary'] );
650  throw new \DomainException(
651  get_class( $provider ) . " returned an invalid username: {$res->username}"
652  );
653  }
654  if ( $user->getId() === 0 ) {
655  // User doesn't exist locally. Create it.
656  $this->logger->info( 'Auto-creating {user} on login', [
657  'user' => $user->getName(),
658  ] );
659  $status = $this->autoCreateUser( $user, $state['primary'], false );
660  if ( !$status->isGood() ) {
662  Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
663  );
664  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
665  $session->remove( 'AuthManager::authnState' );
666  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
667  $ret, $user, $user->getName(), [] );
668  return $ret;
669  }
670  }
671 
672  // Step 3: Iterate over all the secondary authentication providers.
673 
674  $beginReqs = $state['reqs'];
675 
676  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
677  if ( !isset( $state['secondary'][$id] ) ) {
678  // This provider isn't started yet, so we pass it the set
679  // of reqs from beginAuthentication instead of whatever
680  // might have been used by a previous provider in line.
681  $func = 'beginSecondaryAuthentication';
682  $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
683  } elseif ( !$state['secondary'][$id] ) {
684  $func = 'continueSecondaryAuthentication';
685  $res = $provider->continueSecondaryAuthentication( $user, $reqs );
686  } else {
687  continue;
688  }
689  switch ( $res->status ) {
691  $this->logger->debug( "Secondary login with $id succeeded" );
692  // fall through
694  $state['secondary'][$id] = true;
695  break;
697  $this->logger->debug( "Login failed in secondary authentication by $id" );
698  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
699  $session->remove( 'AuthManager::authnState' );
700  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
701  $res, $user, $user->getName(), [] );
702  return $res;
705  $this->logger->debug( "Secondary login with $id returned " . $res->status );
706  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
707  $state['secondary'][$id] = false;
708  $state['continueRequests'] = $res->neededRequests;
709  $session->setSecret( 'AuthManager::authnState', $state );
710  return $res;
711 
712  // @codeCoverageIgnoreStart
713  default:
714  throw new \DomainException(
715  get_class( $provider ) . "::{$func}() returned $res->status"
716  );
717  // @codeCoverageIgnoreEnd
718  }
719  }
720 
721  // Step 4: Authentication complete! Set the user in the session and
722  // clean up.
723 
724  $this->logger->info( 'Login for {user} succeeded from {clientip}', [
725  'user' => $user->getName(),
726  'clientip' => $this->request->getIP(),
727  ] );
730  $beginReqs, RememberMeAuthenticationRequest::class
731  );
732  $this->setSessionDataForUser( $user, $req && $req->rememberMe );
733  $ret = AuthenticationResponse::newPass( $user->getName() );
734  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
735  $session->remove( 'AuthManager::authnState' );
736  $this->removeAuthenticationSessionData( null );
737  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
738  $ret, $user, $user->getName(), [] );
739  return $ret;
740  } catch ( \Exception $ex ) {
741  $session->remove( 'AuthManager::authnState' );
742  throw $ex;
743  }
744  }
745 
757  public function securitySensitiveOperationStatus( $operation ) {
758  $status = self::SEC_OK;
759 
760  $this->logger->debug( __METHOD__ . ": Checking $operation" );
761 
762  $session = $this->request->getSession();
763  $aId = $session->getUser()->getId();
764  if ( $aId === 0 ) {
765  // User isn't authenticated. DWIM?
766  $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
767  $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
768  return $status;
769  }
770 
771  if ( $session->canSetUser() ) {
772  $id = $session->get( 'AuthManager:lastAuthId' );
773  $last = $session->get( 'AuthManager:lastAuthTimestamp' );
774  if ( $id !== $aId || $last === null ) {
775  $timeSinceLogin = PHP_INT_MAX; // Forever ago
776  } else {
777  $timeSinceLogin = max( 0, time() - $last );
778  }
779 
780  $thresholds = $this->config->get( 'ReauthenticateTime' );
781  if ( isset( $thresholds[$operation] ) ) {
782  $threshold = $thresholds[$operation];
783  } elseif ( isset( $thresholds['default'] ) ) {
784  $threshold = $thresholds['default'];
785  } else {
786  throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
787  }
788 
789  if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
790  $status = self::SEC_REAUTH;
791  }
792  } else {
793  $timeSinceLogin = -1;
794 
795  $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
796  if ( isset( $pass[$operation] ) ) {
797  $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
798  } elseif ( isset( $pass['default'] ) ) {
799  $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
800  } else {
801  throw new \UnexpectedValueException(
802  '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
803  );
804  }
805  }
806 
807  $this->getHookRunner()->onSecuritySensitiveOperationStatus(
808  $status, $operation, $session, $timeSinceLogin );
809 
810  // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
811  if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
812  $status = self::SEC_FAIL;
813  }
814 
815  $this->logger->info( __METHOD__ . ": $operation is $status for '{user}'",
816  [
817  'user' => $session->getUser()->getName(),
818  'clientip' => $this->getRequest()->getIP(),
819  ]
820  );
821 
822  return $status;
823  }
824 
834  public function userCanAuthenticate( $username ) {
835  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
836  if ( $provider->testUserCanAuthenticate( $username ) ) {
837  return true;
838  }
839  }
840  return false;
841  }
842 
857  public function normalizeUsername( $username ) {
858  $ret = [];
859  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
860  $normalized = $provider->providerNormalizeUsername( $username );
861  if ( $normalized !== null ) {
862  $ret[$normalized] = true;
863  }
864  }
865  return array_keys( $ret );
866  }
867 
868  // endregion -- end of Authentication
869 
870  /***************************************************************************/
871  // region Authentication data changing
881  public function revokeAccessForUser( $username ) {
882  $this->logger->info( 'Revoking access for {user}', [
883  'user' => $username,
884  ] );
885  $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
886  }
887 
897  public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
898  $any = false;
899  $providers = $this->getPrimaryAuthenticationProviders() +
901 
902  foreach ( $providers as $provider ) {
903  $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
904  if ( !$status->isGood() ) {
905  // If status is not good because reset email password last attempt was within
906  // $wgPasswordReminderResendTime then return good status with throttled-mailpassword value;
907  // otherwise, return the $status wrapped.
908  return $status->hasMessage( 'throttled-mailpassword' )
909  ? Status::newGood( 'throttled-mailpassword' )
910  : Status::wrap( $status );
911  }
912  $any = $any || $status->value !== 'ignored';
913  }
914  if ( !$any ) {
915  $status = Status::newGood( 'ignored' );
916  $status->warning( 'authmanager-change-not-supported' );
917  return $status;
918  }
919  return Status::newGood();
920  }
921 
939  public function changeAuthenticationData( AuthenticationRequest $req, $isAddition = false ) {
940  $this->logger->info( 'Changing authentication data for {user} class {what}', [
941  'user' => is_string( $req->username ) ? $req->username : '<no name>',
942  'what' => get_class( $req ),
943  ] );
944 
945  $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
946 
947  // When the main account's authentication data is changed, invalidate
948  // all BotPasswords too.
949  if ( !$isAddition ) {
951  }
952  }
953 
954  // endregion -- end of Authentication data changing
955 
956  /***************************************************************************/
957  // region Account creation
964  public function canCreateAccounts() {
965  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
966  switch ( $provider->accountCreationType() ) {
969  return true;
970  }
971  }
972  return false;
973  }
974 
983  public function canCreateAccount( $username, $options = [] ) {
984  // Back compat
985  if ( is_int( $options ) ) {
986  $options = [ 'flags' => $options ];
987  }
988  $options += [
989  'flags' => User::READ_NORMAL,
990  'creating' => false,
991  ];
992  $flags = $options['flags'];
993 
994  if ( !$this->canCreateAccounts() ) {
995  return Status::newFatal( 'authmanager-create-disabled' );
996  }
997 
998  if ( $this->userExists( $username, $flags ) ) {
999  return Status::newFatal( 'userexists' );
1000  }
1001 
1002  $user = User::newFromName( $username, 'creatable' );
1003  if ( !is_object( $user ) ) {
1004  return Status::newFatal( 'noname' );
1005  } else {
1006  $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
1007  if ( $user->getId() !== 0 ) {
1008  return Status::newFatal( 'userexists' );
1009  }
1010  }
1011 
1012  // Denied by providers?
1013  $providers = $this->getPreAuthenticationProviders() +
1016  foreach ( $providers as $provider ) {
1017  $status = $provider->testUserForCreation( $user, false, $options );
1018  if ( !$status->isGood() ) {
1019  return Status::wrap( $status );
1020  }
1021  }
1022 
1023  return Status::newGood();
1024  }
1025 
1031  public function checkAccountCreatePermissions( User $creator ) {
1032  // Wiki is read-only?
1033  if ( $this->readOnlyMode->isReadOnly() ) {
1034  return Status::newFatal( wfMessage( 'readonlytext', $this->readOnlyMode->getReason() ) );
1035  }
1036 
1037  $permStatus = new PermissionStatus();
1038  if ( !$creator->authorizeWrite(
1039  'createaccount',
1040  SpecialPage::getTitleFor( 'CreateAccount' ),
1041  $permStatus
1042  ) ) {
1043  return Status::wrap( $permStatus );
1044  }
1045 
1046  $ip = $this->getRequest()->getIP();
1047 
1048  $block = $creator->isBlockedFromCreateAccount();
1049  if ( $block ) {
1050  $language = \RequestContext::getMain()->getLanguage();
1051  return Status::newFatal(
1052  $this->blockErrorFormatter->getMessage( $block, $creator, $language, $ip )
1053  );
1054  }
1055 
1056  if ( $this->blockManager->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
1057  return Status::newFatal( 'sorbs_create_account_reason' );
1058  }
1059 
1060  return Status::newGood();
1061  }
1062 
1082  public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
1083  $session = $this->request->getSession();
1084  if ( !$this->canCreateAccounts() ) {
1085  // Caller should have called canCreateAccounts()
1086  $session->remove( 'AuthManager::accountCreationState' );
1087  throw new \LogicException( 'Account creation is not possible' );
1088  }
1089 
1090  try {
1092  } catch ( \UnexpectedValueException $ex ) {
1093  $username = null;
1094  }
1095  if ( $username === null ) {
1096  $this->logger->debug( __METHOD__ . ': No username provided' );
1097  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1098  }
1099 
1100  // Permissions check
1101  $status = $this->checkAccountCreatePermissions( $creator );
1102  if ( !$status->isGood() ) {
1103  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {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  $status = $this->canCreateAccount(
1112  $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1113  );
1114  if ( !$status->isGood() ) {
1115  $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1116  'user' => $username,
1117  'creator' => $creator->getName(),
1118  'reason' => $status->getWikiText( null, null, 'en' )
1119  ] );
1120  return AuthenticationResponse::newFail( $status->getMessage() );
1121  }
1122 
1123  $user = User::newFromName( $username, 'creatable' );
1124  foreach ( $reqs as $req ) {
1125  $req->username = $username;
1126  $req->returnToUrl = $returnToUrl;
1127  if ( $req instanceof UserDataAuthenticationRequest ) {
1128  $status = $req->populateUser( $user );
1129  if ( !$status->isGood() ) {
1130  $status = Status::wrap( $status );
1131  $session->remove( 'AuthManager::accountCreationState' );
1132  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1133  'user' => $user->getName(),
1134  'creator' => $creator->getName(),
1135  'reason' => $status->getWikiText( null, null, 'en' ),
1136  ] );
1137  return AuthenticationResponse::newFail( $status->getMessage() );
1138  }
1139  }
1140  }
1141 
1142  $this->removeAuthenticationSessionData( null );
1143 
1144  $state = [
1145  'username' => $username,
1146  'userid' => 0,
1147  'creatorid' => $creator->getId(),
1148  'creatorname' => $creator->getName(),
1149  'reqs' => $reqs,
1150  'returnToUrl' => $returnToUrl,
1151  'primary' => null,
1152  'primaryResponse' => null,
1153  'secondary' => [],
1154  'continueRequests' => [],
1155  'maybeLink' => [],
1156  'ranPreTests' => false,
1157  ];
1158 
1159  // Special case: converting a login to an account creation
1161  $reqs, CreateFromLoginAuthenticationRequest::class
1162  );
1163  if ( $req ) {
1164  $state['maybeLink'] = $req->maybeLink;
1165 
1166  if ( $req->createRequest ) {
1167  $reqs[] = $req->createRequest;
1168  $state['reqs'][] = $req->createRequest;
1169  }
1170  }
1171 
1172  $session->setSecret( 'AuthManager::accountCreationState', $state );
1173  $session->persist();
1174 
1175  return $this->continueAccountCreation( $reqs );
1176  }
1177 
1183  public function continueAccountCreation( array $reqs ) {
1184  $session = $this->request->getSession();
1185  try {
1186  if ( !$this->canCreateAccounts() ) {
1187  // Caller should have called canCreateAccounts()
1188  $session->remove( 'AuthManager::accountCreationState' );
1189  throw new \LogicException( 'Account creation is not possible' );
1190  }
1191 
1192  $state = $session->getSecret( 'AuthManager::accountCreationState' );
1193  if ( !is_array( $state ) ) {
1195  wfMessage( 'authmanager-create-not-in-progress' )
1196  );
1197  }
1198  $state['continueRequests'] = [];
1199 
1200  // Step 0: Prepare and validate the input
1201 
1202  $user = User::newFromName( $state['username'], 'creatable' );
1203  if ( !is_object( $user ) ) {
1204  $session->remove( 'AuthManager::accountCreationState' );
1205  $this->logger->debug( __METHOD__ . ': Invalid username', [
1206  'user' => $state['username'],
1207  ] );
1208  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1209  }
1210 
1211  if ( $state['creatorid'] ) {
1212  $creator = User::newFromId( $state['creatorid'] );
1213  } else {
1214  $creator = new User;
1215  $creator->setName( $state['creatorname'] );
1216  }
1217 
1218  // Avoid account creation races on double submissions
1220  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1221  if ( !$lock ) {
1222  // Don't clear AuthManager::accountCreationState for this code
1223  // path because the process that won the race owns it.
1224  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1225  'user' => $user->getName(),
1226  'creator' => $creator->getName(),
1227  ] );
1228  return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1229  }
1230 
1231  // Permissions check
1232  $status = $this->checkAccountCreatePermissions( $creator );
1233  if ( !$status->isGood() ) {
1234  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1235  'user' => $user->getName(),
1236  'creator' => $creator->getName(),
1237  'reason' => $status->getWikiText( null, null, 'en' )
1238  ] );
1239  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1240  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1241  $session->remove( 'AuthManager::accountCreationState' );
1242  return $ret;
1243  }
1244 
1245  // Load from master for existence check
1246  $user->load( User::READ_LOCKING );
1247 
1248  if ( $state['userid'] === 0 ) {
1249  if ( $user->getId() !== 0 ) {
1250  $this->logger->debug( __METHOD__ . ': User exists locally', [
1251  'user' => $user->getName(),
1252  'creator' => $creator->getName(),
1253  ] );
1254  $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1255  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1256  $session->remove( 'AuthManager::accountCreationState' );
1257  return $ret;
1258  }
1259  } else {
1260  if ( $user->getId() === 0 ) {
1261  $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1262  'user' => $user->getName(),
1263  'creator' => $creator->getName(),
1264  'expected_id' => $state['userid'],
1265  ] );
1266  throw new \UnexpectedValueException(
1267  "User \"{$state['username']}\" should exist now, but doesn't!"
1268  );
1269  }
1270  if ( $user->getId() !== $state['userid'] ) {
1271  $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1272  'user' => $user->getName(),
1273  'creator' => $creator->getName(),
1274  'expected_id' => $state['userid'],
1275  'actual_id' => $user->getId(),
1276  ] );
1277  throw new \UnexpectedValueException(
1278  "User \"{$state['username']}\" exists, but " .
1279  "ID {$user->getId()} !== {$state['userid']}!"
1280  );
1281  }
1282  }
1283  foreach ( $state['reqs'] as $req ) {
1284  if ( $req instanceof UserDataAuthenticationRequest ) {
1285  $status = $req->populateUser( $user );
1286  if ( !$status->isGood() ) {
1287  // This should never happen...
1288  $status = Status::wrap( $status );
1289  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1290  'user' => $user->getName(),
1291  'creator' => $creator->getName(),
1292  'reason' => $status->getWikiText( null, null, 'en' ),
1293  ] );
1294  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1295  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1296  $session->remove( 'AuthManager::accountCreationState' );
1297  return $ret;
1298  }
1299  }
1300  }
1301 
1302  foreach ( $reqs as $req ) {
1303  $req->returnToUrl = $state['returnToUrl'];
1304  $req->username = $state['username'];
1305  }
1306 
1307  // Run pre-creation tests, if we haven't already
1308  if ( !$state['ranPreTests'] ) {
1309  $providers = $this->getPreAuthenticationProviders() +
1312  foreach ( $providers as $id => $provider ) {
1313  $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1314  if ( !$status->isGood() ) {
1315  $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1316  'user' => $user->getName(),
1317  'creator' => $creator->getName(),
1318  ] );
1320  Status::wrap( $status )->getMessage()
1321  );
1322  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1323  $session->remove( 'AuthManager::accountCreationState' );
1324  return $ret;
1325  }
1326  }
1327 
1328  $state['ranPreTests'] = true;
1329  }
1330 
1331  // Step 1: Choose a primary authentication provider and call it until it succeeds.
1332 
1333  if ( $state['primary'] === null ) {
1334  // We haven't picked a PrimaryAuthenticationProvider yet
1335  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1336  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1337  continue;
1338  }
1339  $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1340  switch ( $res->status ) {
1342  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1343  'user' => $user->getName(),
1344  'creator' => $creator->getName(),
1345  ] );
1346  $state['primary'] = $id;
1347  $state['primaryResponse'] = $res;
1348  break 2;
1350  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1351  'user' => $user->getName(),
1352  'creator' => $creator->getName(),
1353  ] );
1354  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1355  $session->remove( 'AuthManager::accountCreationState' );
1356  return $res;
1358  // Continue loop
1359  break;
1362  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1363  'user' => $user->getName(),
1364  'creator' => $creator->getName(),
1365  ] );
1366  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1367  $state['primary'] = $id;
1368  $state['continueRequests'] = $res->neededRequests;
1369  $session->setSecret( 'AuthManager::accountCreationState', $state );
1370  return $res;
1371 
1372  // @codeCoverageIgnoreStart
1373  default:
1374  throw new \DomainException(
1375  get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1376  );
1377  // @codeCoverageIgnoreEnd
1378  }
1379  }
1380  if ( $state['primary'] === null ) {
1381  $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1382  'user' => $user->getName(),
1383  'creator' => $creator->getName(),
1384  ] );
1386  wfMessage( 'authmanager-create-no-primary' )
1387  );
1388  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1389  $session->remove( 'AuthManager::accountCreationState' );
1390  return $ret;
1391  }
1392  } elseif ( $state['primaryResponse'] === null ) {
1393  $provider = $this->getAuthenticationProvider( $state['primary'] );
1394  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1395  // Configuration changed? Force them to start over.
1396  // @codeCoverageIgnoreStart
1398  wfMessage( 'authmanager-create-not-in-progress' )
1399  );
1400  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1401  $session->remove( 'AuthManager::accountCreationState' );
1402  return $ret;
1403  // @codeCoverageIgnoreEnd
1404  }
1405  $id = $provider->getUniqueId();
1406  $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1407  switch ( $res->status ) {
1409  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1410  'user' => $user->getName(),
1411  'creator' => $creator->getName(),
1412  ] );
1413  $state['primaryResponse'] = $res;
1414  break;
1416  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1417  'user' => $user->getName(),
1418  'creator' => $creator->getName(),
1419  ] );
1420  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1421  $session->remove( 'AuthManager::accountCreationState' );
1422  return $res;
1425  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1426  'user' => $user->getName(),
1427  'creator' => $creator->getName(),
1428  ] );
1429  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1430  $state['continueRequests'] = $res->neededRequests;
1431  $session->setSecret( 'AuthManager::accountCreationState', $state );
1432  return $res;
1433  default:
1434  throw new \DomainException(
1435  get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1436  );
1437  }
1438  }
1439 
1440  // Step 2: Primary authentication succeeded, create the User object
1441  // and add the user locally.
1442 
1443  if ( $state['userid'] === 0 ) {
1444  $this->logger->info( 'Creating user {user} during account creation', [
1445  'user' => $user->getName(),
1446  'creator' => $creator->getName(),
1447  ] );
1448  $status = $user->addToDatabase();
1449  if ( !$status->isOK() ) {
1450  // @codeCoverageIgnoreStart
1451  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1452  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1453  $session->remove( 'AuthManager::accountCreationState' );
1454  return $ret;
1455  // @codeCoverageIgnoreEnd
1456  }
1457  $this->setDefaultUserOptions( $user, $creator->isAnon() );
1458  $this->getHookRunner()->onLocalUserCreated( $user, false );
1459  $user->saveSettings();
1460  $state['userid'] = $user->getId();
1461 
1462  // Update user count
1463  \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1464 
1465  // Watch user's userpage and talk page
1466  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1467 
1468  // Inform the provider
1469  $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1470 
1471  // Log the creation
1472  if ( $this->config->get( 'NewUserLog' ) ) {
1473  $isAnon = $creator->isAnon();
1474  $logEntry = new \ManualLogEntry(
1475  'newusers',
1476  $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1477  );
1478  $logEntry->setPerformer( $isAnon ? $user : $creator );
1479  $logEntry->setTarget( $user->getUserPage() );
1482  $state['reqs'], CreationReasonAuthenticationRequest::class
1483  );
1484  $logEntry->setComment( $req ? $req->reason : '' );
1485  $logEntry->setParameters( [
1486  '4::userid' => $user->getId(),
1487  ] );
1488  $logid = $logEntry->insert();
1489  $logEntry->publish( $logid );
1490  }
1491  }
1492 
1493  // Step 3: Iterate over all the secondary authentication providers.
1494 
1495  $beginReqs = $state['reqs'];
1496 
1497  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1498  if ( !isset( $state['secondary'][$id] ) ) {
1499  // This provider isn't started yet, so we pass it the set
1500  // of reqs from beginAuthentication instead of whatever
1501  // might have been used by a previous provider in line.
1502  $func = 'beginSecondaryAccountCreation';
1503  $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1504  } elseif ( !$state['secondary'][$id] ) {
1505  $func = 'continueSecondaryAccountCreation';
1506  $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1507  } else {
1508  continue;
1509  }
1510  switch ( $res->status ) {
1512  $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1513  'user' => $user->getName(),
1514  'creator' => $creator->getName(),
1515  ] );
1516  // fall through
1518  $state['secondary'][$id] = true;
1519  break;
1522  $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1523  'user' => $user->getName(),
1524  'creator' => $creator->getName(),
1525  ] );
1526  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1527  $state['secondary'][$id] = false;
1528  $state['continueRequests'] = $res->neededRequests;
1529  $session->setSecret( 'AuthManager::accountCreationState', $state );
1530  return $res;
1532  throw new \DomainException(
1533  get_class( $provider ) . "::{$func}() returned $res->status." .
1534  ' Secondary providers are not allowed to fail account creation, that' .
1535  ' should have been done via testForAccountCreation().'
1536  );
1537  // @codeCoverageIgnoreStart
1538  default:
1539  throw new \DomainException(
1540  get_class( $provider ) . "::{$func}() returned $res->status"
1541  );
1542  // @codeCoverageIgnoreEnd
1543  }
1544  }
1545 
1546  $id = $user->getId();
1547  $name = $user->getName();
1548  $req = new CreatedAccountAuthenticationRequest( $id, $name );
1549  $ret = AuthenticationResponse::newPass( $name );
1550  $ret->loginRequest = $req;
1551  $this->createdAccountAuthenticationRequests[] = $req;
1552 
1553  $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1554  'user' => $user->getName(),
1555  'creator' => $creator->getName(),
1556  ] );
1557 
1558  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1559  $session->remove( 'AuthManager::accountCreationState' );
1560  $this->removeAuthenticationSessionData( null );
1561  return $ret;
1562  } catch ( \Exception $ex ) {
1563  $session->remove( 'AuthManager::accountCreationState' );
1564  throw $ex;
1565  }
1566  }
1567 
1586  public function autoCreateUser( User $user, $source, $login = true, $log = true ) {
1587  if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1588  $source !== self::AUTOCREATE_SOURCE_MAINT &&
1590  ) {
1591  throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1592  }
1593 
1594  $username = $user->getName();
1595 
1596  // Try the local user from the replica DB
1597  $localId = User::idFromName( $username );
1598  $flags = User::READ_NORMAL;
1599 
1600  // Fetch the user ID from the master, so that we don't try to create the user
1601  // when they already exist, due to replication lag
1602  // @codeCoverageIgnoreStart
1603  if (
1604  !$localId &&
1605  MediaWikiServices::getInstance()->getDBLoadBalancer()->getReaderIndex() !== 0
1606  ) {
1607  $localId = User::idFromName( $username, User::READ_LATEST );
1608  $flags = User::READ_LATEST;
1609  }
1610  // @codeCoverageIgnoreEnd
1611 
1612  if ( $localId ) {
1613  $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1614  'username' => $username,
1615  ] );
1616  $user->setId( $localId );
1617  $user->loadFromId( $flags );
1618  if ( $login ) {
1619  $this->setSessionDataForUser( $user );
1620  }
1621  $status = Status::newGood();
1622  $status->warning( 'userexists' );
1623  return $status;
1624  }
1625 
1626  // Wiki is read-only?
1627  if ( $this->readOnlyMode->isReadOnly() ) {
1628  $reason = $this->readOnlyMode->getReason();
1629  $this->logger->debug( __METHOD__ . ': denied because of read only mode: {reason}', [
1630  'username' => $username,
1631  'reason' => $reason,
1632  ] );
1633  $user->setId( 0 );
1634  $user->loadFromId();
1635  return Status::newFatal( wfMessage( 'readonlytext', $reason ) );
1636  }
1637 
1638  // Check the session, if we tried to create this user already there's
1639  // no point in retrying.
1640  $session = $this->request->getSession();
1641  if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1642  $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1643  'username' => $username,
1644  'sessionid' => $session->getId(),
1645  ] );
1646  $user->setId( 0 );
1647  $user->loadFromId();
1648  $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1649  if ( $reason instanceof StatusValue ) {
1650  return Status::wrap( $reason );
1651  } else {
1652  return Status::newFatal( $reason );
1653  }
1654  }
1655 
1656  // Is the username creatable?
1657  if ( !$this->userNameUtils->isCreatable( $username ) ) {
1658  $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1659  'username' => $username,
1660  ] );
1661  $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
1662  $user->setId( 0 );
1663  $user->loadFromId();
1664  return Status::newFatal( 'noname' );
1665  }
1666 
1667  // Is the IP user able to create accounts?
1668  $anon = new User;
1669  if ( $source !== self::AUTOCREATE_SOURCE_MAINT &&
1670  !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' )
1671  ) {
1672  $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1673  'username' => $username,
1674  'clientip' => $anon->getName(),
1675  ] );
1676  $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
1677  $session->persist();
1678  $user->setId( 0 );
1679  $user->loadFromId();
1680  return Status::newFatal( 'authmanager-autocreate-noperm' );
1681  }
1682 
1683  // Avoid account creation races on double submissions
1685  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1686  if ( !$lock ) {
1687  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1688  'user' => $username,
1689  ] );
1690  $user->setId( 0 );
1691  $user->loadFromId();
1692  return Status::newFatal( 'usernameinprogress' );
1693  }
1694 
1695  // Denied by providers?
1696  $options = [
1697  'flags' => User::READ_LATEST,
1698  'creating' => true,
1699  ];
1700  $providers = $this->getPreAuthenticationProviders() +
1703  foreach ( $providers as $provider ) {
1704  $status = $provider->testUserForCreation( $user, $source, $options );
1705  if ( !$status->isGood() ) {
1706  $ret = Status::wrap( $status );
1707  $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1708  'username' => $username,
1709  'reason' => $ret->getWikiText( null, null, 'en' ),
1710  ] );
1711  $session->set( 'AuthManager::AutoCreateBlacklist', $status );
1712  $user->setId( 0 );
1713  $user->loadFromId();
1714  return $ret;
1715  }
1716  }
1717 
1718  $backoffKey = $cache->makeKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1719  if ( $cache->get( $backoffKey ) ) {
1720  $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1721  'username' => $username,
1722  ] );
1723  $user->setId( 0 );
1724  $user->loadFromId();
1725  return Status::newFatal( 'authmanager-autocreate-exception' );
1726  }
1727 
1728  // Checks passed, create the user...
1729  $from = $_SERVER['REQUEST_URI'] ?? 'CLI';
1730  $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1731  'username' => $username,
1732  'from' => $from,
1733  ] );
1734 
1735  // Ignore warnings about master connections/writes...hard to avoid here
1736  $trxProfiler = \Profiler::instance()->getTransactionProfiler();
1737  $old = $trxProfiler->setSilenced( true );
1738  try {
1739  $status = $user->addToDatabase();
1740  if ( !$status->isOK() ) {
1741  // Double-check for a race condition (T70012). We make use of the fact that when
1742  // addToDatabase fails due to the user already existing, the user object gets loaded.
1743  if ( $user->getId() ) {
1744  $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1745  'username' => $username,
1746  ] );
1747  if ( $login ) {
1748  $this->setSessionDataForUser( $user );
1749  }
1750  $status = Status::newGood();
1751  $status->warning( 'userexists' );
1752  } else {
1753  $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
1754  'username' => $username,
1755  'msg' => $status->getWikiText( null, null, 'en' )
1756  ] );
1757  $user->setId( 0 );
1758  $user->loadFromId();
1759  }
1760  return $status;
1761  }
1762  } catch ( \Exception $ex ) {
1763  $trxProfiler->setSilenced( $old );
1764  $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1765  'username' => $username,
1766  'exception' => $ex,
1767  ] );
1768  // Do not keep throwing errors for a while
1769  $cache->set( $backoffKey, 1, 600 );
1770  // Bubble up error; which should normally trigger DB rollbacks
1771  throw $ex;
1772  }
1773 
1774  $this->setDefaultUserOptions( $user, false );
1775 
1776  // Inform the providers
1777  $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1778 
1779  $this->getHookRunner()->onLocalUserCreated( $user, true );
1780  $user->saveSettings();
1781 
1782  // Update user count
1783  \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1784  // Watch user's userpage and talk page
1785  \DeferredUpdates::addCallableUpdate( static function () use ( $user ) {
1786  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1787  } );
1788 
1789  // Log the creation
1790  if ( $this->config->get( 'NewUserLog' ) && $log ) {
1791  $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1792  $logEntry->setPerformer( $user );
1793  $logEntry->setTarget( $user->getUserPage() );
1794  $logEntry->setComment( '' );
1795  $logEntry->setParameters( [
1796  '4::userid' => $user->getId(),
1797  ] );
1798  $logEntry->insert();
1799  }
1800 
1801  $trxProfiler->setSilenced( $old );
1802 
1803  if ( $login ) {
1804  $this->setSessionDataForUser( $user );
1805  }
1806 
1807  return Status::newGood();
1808  }
1809 
1810  // endregion -- end of Account creation
1811 
1812  /***************************************************************************/
1813  // region Account linking
1820  public function canLinkAccounts() {
1821  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1822  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1823  return true;
1824  }
1825  }
1826  return false;
1827  }
1828 
1838  public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1839  $session = $this->request->getSession();
1840  $session->remove( 'AuthManager::accountLinkState' );
1841 
1842  if ( !$this->canLinkAccounts() ) {
1843  // Caller should have called canLinkAccounts()
1844  throw new \LogicException( 'Account linking is not possible' );
1845  }
1846 
1847  if ( $user->getId() === 0 ) {
1848  if ( !$this->userNameUtils->isUsable( $user->getName() ) ) {
1849  $msg = wfMessage( 'noname' );
1850  } else {
1851  $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1852  }
1853  return AuthenticationResponse::newFail( $msg );
1854  }
1855  foreach ( $reqs as $req ) {
1856  $req->username = $user->getName();
1857  $req->returnToUrl = $returnToUrl;
1858  }
1859 
1860  $this->removeAuthenticationSessionData( null );
1861 
1862  $providers = $this->getPreAuthenticationProviders();
1863  foreach ( $providers as $id => $provider ) {
1864  $status = $provider->testForAccountLink( $user );
1865  if ( !$status->isGood() ) {
1866  $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1867  'user' => $user->getName(),
1868  ] );
1870  Status::wrap( $status )->getMessage()
1871  );
1872  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1873  return $ret;
1874  }
1875  }
1876 
1877  $state = [
1878  'username' => $user->getName(),
1879  'userid' => $user->getId(),
1880  'returnToUrl' => $returnToUrl,
1881  'primary' => null,
1882  'continueRequests' => [],
1883  ];
1884 
1885  $providers = $this->getPrimaryAuthenticationProviders();
1886  foreach ( $providers as $id => $provider ) {
1887  if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1888  continue;
1889  }
1890 
1891  $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1892  switch ( $res->status ) {
1894  $this->logger->info( "Account linked to {user} by $id", [
1895  'user' => $user->getName(),
1896  ] );
1897  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1898  return $res;
1899 
1901  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1902  'user' => $user->getName(),
1903  ] );
1904  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1905  return $res;
1906 
1908  // Continue loop
1909  break;
1910 
1913  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1914  'user' => $user->getName(),
1915  ] );
1916  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1917  $state['primary'] = $id;
1918  $state['continueRequests'] = $res->neededRequests;
1919  $session->setSecret( 'AuthManager::accountLinkState', $state );
1920  $session->persist();
1921  return $res;
1922 
1923  // @codeCoverageIgnoreStart
1924  default:
1925  throw new \DomainException(
1926  get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1927  );
1928  // @codeCoverageIgnoreEnd
1929  }
1930  }
1931 
1932  $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1933  'user' => $user->getName(),
1934  ] );
1936  wfMessage( 'authmanager-link-no-primary' )
1937  );
1938  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1939  return $ret;
1940  }
1941 
1947  public function continueAccountLink( array $reqs ) {
1948  $session = $this->request->getSession();
1949  try {
1950  if ( !$this->canLinkAccounts() ) {
1951  // Caller should have called canLinkAccounts()
1952  $session->remove( 'AuthManager::accountLinkState' );
1953  throw new \LogicException( 'Account linking is not possible' );
1954  }
1955 
1956  $state = $session->getSecret( 'AuthManager::accountLinkState' );
1957  if ( !is_array( $state ) ) {
1959  wfMessage( 'authmanager-link-not-in-progress' )
1960  );
1961  }
1962  $state['continueRequests'] = [];
1963 
1964  // Step 0: Prepare and validate the input
1965 
1966  $user = User::newFromName( $state['username'], 'usable' );
1967  if ( !is_object( $user ) ) {
1968  $session->remove( 'AuthManager::accountLinkState' );
1969  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1970  }
1971  if ( $user->getId() !== $state['userid'] ) {
1972  throw new \UnexpectedValueException(
1973  "User \"{$state['username']}\" is valid, but " .
1974  "ID {$user->getId()} !== {$state['userid']}!"
1975  );
1976  }
1977 
1978  foreach ( $reqs as $req ) {
1979  $req->username = $state['username'];
1980  $req->returnToUrl = $state['returnToUrl'];
1981  }
1982 
1983  // Step 1: Call the primary again until it succeeds
1984 
1985  $provider = $this->getAuthenticationProvider( $state['primary'] );
1986  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1987  // Configuration changed? Force them to start over.
1988  // @codeCoverageIgnoreStart
1990  wfMessage( 'authmanager-link-not-in-progress' )
1991  );
1992  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1993  $session->remove( 'AuthManager::accountLinkState' );
1994  return $ret;
1995  // @codeCoverageIgnoreEnd
1996  }
1997  $id = $provider->getUniqueId();
1998  $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1999  switch ( $res->status ) {
2001  $this->logger->info( "Account linked to {user} by $id", [
2002  'user' => $user->getName(),
2003  ] );
2004  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
2005  $session->remove( 'AuthManager::accountLinkState' );
2006  return $res;
2008  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
2009  'user' => $user->getName(),
2010  ] );
2011  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
2012  $session->remove( 'AuthManager::accountLinkState' );
2013  return $res;
2016  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
2017  'user' => $user->getName(),
2018  ] );
2019  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
2020  $state['continueRequests'] = $res->neededRequests;
2021  $session->setSecret( 'AuthManager::accountLinkState', $state );
2022  return $res;
2023  default:
2024  throw new \DomainException(
2025  get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
2026  );
2027  }
2028  } catch ( \Exception $ex ) {
2029  $session->remove( 'AuthManager::accountLinkState' );
2030  throw $ex;
2031  }
2032  }
2033 
2034  // endregion -- end of Account linking
2035 
2036  /***************************************************************************/
2037  // region Information methods
2058  public function getAuthenticationRequests( $action, User $user = null ) {
2059  $options = [];
2060  $providerAction = $action;
2061 
2062  // Figure out which providers to query
2063  switch ( $action ) {
2064  case self::ACTION_LOGIN:
2065  case self::ACTION_CREATE:
2066  $providers = $this->getPreAuthenticationProviders() +
2069  break;
2070 
2072  $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
2073  return is_array( $state ) ? $state['continueRequests'] : [];
2074 
2076  $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
2077  return is_array( $state ) ? $state['continueRequests'] : [];
2078 
2079  case self::ACTION_LINK:
2080  $providers = [];
2081  foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2082  if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2083  $providers[] = $p;
2084  }
2085  }
2086  break;
2087 
2088  case self::ACTION_UNLINK:
2089  $providers = [];
2090  foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2091  if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2092  $providers[] = $p;
2093  }
2094  }
2095 
2096  // To providers, unlink and remove are identical.
2097  $providerAction = self::ACTION_REMOVE;
2098  break;
2099 
2101  $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
2102  return is_array( $state ) ? $state['continueRequests'] : [];
2103 
2104  case self::ACTION_CHANGE:
2105  case self::ACTION_REMOVE:
2106  $providers = $this->getPrimaryAuthenticationProviders() +
2108  break;
2109 
2110  // @codeCoverageIgnoreStart
2111  default:
2112  throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2113  }
2114  // @codeCoverageIgnoreEnd
2115 
2116  return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2117  }
2118 
2129  $providerAction, array $options, array $providers, User $user = null
2130  ) {
2131  $user = $user ?: \RequestContext::getMain()->getUser();
2132  $options['username'] = $user->isAnon() ? null : $user->getName();
2133 
2134  // Query them and merge results
2135  $reqs = [];
2136  foreach ( $providers as $provider ) {
2137  $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2138  foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2139  $id = $req->getUniqueId();
2140 
2141  // If a required request if from a Primary, mark it as "primary-required" instead
2142  if ( $isPrimary && $req->required ) {
2143  $req->required = AuthenticationRequest::PRIMARY_REQUIRED;
2144  }
2145 
2146  if (
2147  !isset( $reqs[$id] )
2148  || $req->required === AuthenticationRequest::REQUIRED
2149  || $reqs[$id] === AuthenticationRequest::OPTIONAL
2150  ) {
2151  $reqs[$id] = $req;
2152  }
2153  }
2154  }
2155 
2156  // AuthManager has its own req for some actions
2157  switch ( $providerAction ) {
2158  case self::ACTION_LOGIN:
2159  $reqs[] = new RememberMeAuthenticationRequest;
2160  break;
2161 
2162  case self::ACTION_CREATE:
2163  $reqs[] = new UsernameAuthenticationRequest;
2164  $reqs[] = new UserDataAuthenticationRequest;
2165  if ( $options['username'] !== null ) {
2167  $options['username'] = null; // Don't fill in the username below
2168  }
2169  break;
2170  }
2171 
2172  // Fill in reqs data
2173  $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2174 
2175  // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2176  if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2177  $reqs = array_filter( $reqs, function ( $req ) {
2178  return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2179  } );
2180  }
2181 
2182  return array_values( $reqs );
2183  }
2184 
2192  private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2193  foreach ( $reqs as $req ) {
2194  if ( !$req->action || $forceAction ) {
2195  $req->action = $action;
2196  }
2197  if ( $req->username === null ) {
2198  $req->username = $username;
2199  }
2200  }
2201  }
2202 
2209  public function userExists( $username, $flags = User::READ_NORMAL ) {
2210  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2211  if ( $provider->testUserExists( $username, $flags ) ) {
2212  return true;
2213  }
2214  }
2215 
2216  return false;
2217  }
2218 
2230  public function allowsPropertyChange( $property ) {
2231  $providers = $this->getPrimaryAuthenticationProviders() +
2233  foreach ( $providers as $provider ) {
2234  if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2235  return false;
2236  }
2237  }
2238  return true;
2239  }
2240 
2249  public function getAuthenticationProvider( $id ) {
2250  // Fast version
2251  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2252  return $this->allAuthenticationProviders[$id];
2253  }
2254 
2255  // Slow version: instantiate each kind and check
2256  $providers = $this->getPrimaryAuthenticationProviders();
2257  if ( isset( $providers[$id] ) ) {
2258  return $providers[$id];
2259  }
2260  $providers = $this->getSecondaryAuthenticationProviders();
2261  if ( isset( $providers[$id] ) ) {
2262  return $providers[$id];
2263  }
2264  $providers = $this->getPreAuthenticationProviders();
2265  if ( isset( $providers[$id] ) ) {
2266  return $providers[$id];
2267  }
2268 
2269  return null;
2270  }
2271 
2272  // endregion -- end of Information methods
2273 
2274  /***************************************************************************/
2275  // region Internal methods
2284  public function setAuthenticationSessionData( $key, $data ) {
2285  $session = $this->request->getSession();
2286  $arr = $session->getSecret( 'authData' );
2287  if ( !is_array( $arr ) ) {
2288  $arr = [];
2289  }
2290  $arr[$key] = $data;
2291  $session->setSecret( 'authData', $arr );
2292  }
2293 
2301  public function getAuthenticationSessionData( $key, $default = null ) {
2302  $arr = $this->request->getSession()->getSecret( 'authData' );
2303  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2304  return $arr[$key];
2305  } else {
2306  return $default;
2307  }
2308  }
2309 
2315  public function removeAuthenticationSessionData( $key ) {
2316  $session = $this->request->getSession();
2317  if ( $key === null ) {
2318  $session->remove( 'authData' );
2319  } else {
2320  $arr = $session->getSecret( 'authData' );
2321  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2322  unset( $arr[$key] );
2323  $session->setSecret( 'authData', $arr );
2324  }
2325  }
2326  }
2327 
2334  protected function providerArrayFromSpecs( $class, array $specs ) {
2335  $i = 0;
2336  foreach ( $specs as &$spec ) {
2337  $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2338  }
2339  unset( $spec );
2340  // Sort according to the 'sort' field, and if they are equal, according to 'sort2'
2341  usort( $specs, static function ( $a, $b ) {
2342  return $a['sort'] <=> $b['sort']
2343  ?: $a['sort2'] <=> $b['sort2'];
2344  } );
2345 
2346  $ret = [];
2347  foreach ( $specs as $spec ) {
2349  $provider = $this->objectFactory->createObject( $spec, [ 'assertClass' => $class ] );
2350  $provider->setLogger( $this->logger );
2351  $provider->setManager( $this );
2352  $provider->setConfig( $this->config );
2353  $provider->setHookContainer( $this->getHookContainer() );
2354  $id = $provider->getUniqueId();
2355  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2356  throw new \RuntimeException(
2357  "Duplicate specifications for id $id (classes " .
2358  get_class( $provider ) . ' and ' .
2359  get_class( $this->allAuthenticationProviders[$id] ) . ')'
2360  );
2361  }
2362  $this->allAuthenticationProviders[$id] = $provider;
2363  $ret[$id] = $provider;
2364  }
2365  return $ret;
2366  }
2367 
2371  private function getConfiguration() {
2372  return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2373  }
2374 
2379  protected function getPreAuthenticationProviders() {
2380  if ( $this->preAuthenticationProviders === null ) {
2381  $conf = $this->getConfiguration();
2382  $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2383  PreAuthenticationProvider::class, $conf['preauth']
2384  );
2385  }
2387  }
2388 
2393  protected function getPrimaryAuthenticationProviders() {
2394  if ( $this->primaryAuthenticationProviders === null ) {
2395  $conf = $this->getConfiguration();
2396  $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2397  PrimaryAuthenticationProvider::class, $conf['primaryauth']
2398  );
2399  }
2401  }
2402 
2408  if ( $this->secondaryAuthenticationProviders === null ) {
2409  $conf = $this->getConfiguration();
2410  $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2411  SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2412  );
2413  }
2415  }
2416 
2422  private function setSessionDataForUser( $user, $remember = null ) {
2423  $session = $this->request->getSession();
2424  $delay = $session->delaySave();
2425 
2426  $session->resetId();
2427  $session->resetAllTokens();
2428  if ( $session->canSetUser() ) {
2429  $session->setUser( $user );
2430  }
2431  if ( $remember !== null ) {
2432  $session->setRememberUser( $remember );
2433  }
2434  $session->set( 'AuthManager:lastAuthId', $user->getId() );
2435  $session->set( 'AuthManager:lastAuthTimestamp', time() );
2436  $session->persist();
2437 
2438  \Wikimedia\ScopedCallback::consume( $delay );
2439 
2440  $this->getHookRunner()->onUserLoggedIn( $user );
2441  }
2442 
2447  private function setDefaultUserOptions( User $user, $useContextLang ) {
2448  $user->setToken();
2449 
2450  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2451 
2452  $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $contLang;
2453  $user->setOption( 'language', $lang->getPreferredVariant() );
2454 
2455  $contLangConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
2456  ->getLanguageConverter();
2457  if ( $contLangConverter->hasVariants() ) {
2458  $user->setOption( 'variant', $contLangConverter->getPreferredVariant() );
2459  }
2460  }
2461 
2467  private function callMethodOnProviders( $which, $method, array $args ) {
2468  $providers = [];
2469  if ( $which & 1 ) {
2470  $providers += $this->getPreAuthenticationProviders();
2471  }
2472  if ( $which & 2 ) {
2473  $providers += $this->getPrimaryAuthenticationProviders();
2474  }
2475  if ( $which & 4 ) {
2476  $providers += $this->getSecondaryAuthenticationProviders();
2477  }
2478  foreach ( $providers as $provider ) {
2479  $provider->$method( ...$args );
2480  }
2481  }
2482 
2488  public static function resetCache() {
2489  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2490  // @codeCoverageIgnoreStart
2491  throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2492  // @codeCoverageIgnoreEnd
2493  }
2494 
2495  self::$instance = null;
2496  }
2497 
2501  private function getHookContainer() {
2502  return $this->hookContainer;
2503  }
2504 
2508  private function getHookRunner() {
2509  return $this->hookRunner;
2510  }
2511 
2512  // endregion -- end of Internal methods
2513 
2514 }
2515 
2516 /*
2517  * This file uses VisualStudio style region/endregion fold markers which are
2518  * recognised by PHPStorm. If modelines are enabled, the following editor
2519  * configuration will also enable folding in vim, if it is in the last 5 lines
2520  * of the file. We also use "@name" which creates sections in Doxygen.
2521  *
2522  * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
2523  */
MediaWiki\Auth\AuthManager\autoCreateUser
autoCreateUser(User $user, $source, $login=true, $log=true)
Auto-create an account, and optionally log into that account.
Definition: AuthManager.php:1586
MediaWiki\Auth\AuthManager\continueAccountLink
continueAccountLink(array $reqs)
Continue an account linking flow.
Definition: AuthManager.php:1947
MediaWiki\Auth\AuthManager\__construct
__construct(WebRequest $request, Config $config, ObjectFactory $objectFactory, HookContainer $hookContainer, ReadOnlyMode $readOnlyMode, UserNameUtils $userNameUtils, BlockManager $blockManager, BlockErrorFormatter $blockErrorFormatter)
Definition: AuthManager.php:199
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:446
MediaWiki\Auth\AuthManager\continueAuthentication
continueAuthentication(array $reqs)
Continue an authentication flow.
Definition: AuthManager.php:444
MediaWiki\Auth\AuthenticationRequest\OPTIONAL
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
Definition: AuthenticationRequest.php:41
MediaWiki\Auth\AuthManager\getRequest
getRequest()
Definition: AuthManager.php:231
MediaWiki\Auth\PrimaryAuthenticationProvider\TYPE_CREATE
const TYPE_CREATE
Provider can create accounts.
Definition: PrimaryAuthenticationProvider.php:77
MediaWiki\Auth\AuthManager\$preAuthenticationProviders
PreAuthenticationProvider[] $preAuthenticationProviders
Definition: AuthManager.php:154
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:623
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:43
MediaWiki\Auth\AuthManager\SEC_REAUTH
const SEC_REAUTH
Security-sensitive operations should re-authenticate.
Definition: AuthManager.php:122
MediaWiki\Auth\PrimaryAuthenticationProvider\TYPE_NONE
const TYPE_NONE
Provider cannot create or link to accounts.
Definition: PrimaryAuthenticationProvider.php:81
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Auth\AuthenticationProvider\getUniqueId
getUniqueId()
Return a unique identifier for this instance.
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:272
MediaWiki\Auth\AuthManager\fillRequests
fillRequests(array &$reqs, $action, $username, $forceAction=false)
Set values in an array of requests.
Definition: AuthManager.php:2192
MediaWiki\Auth\AuthManager\changeAuthenticationData
changeAuthenticationData(AuthenticationRequest $req, $isAddition=false)
Change authentication data (e.g.
Definition: AuthManager.php:939
MediaWiki\Auth\AuthManager\getPrimaryAuthenticationProviders
getPrimaryAuthenticationProviders()
Get the list of PrimaryAuthenticationProviders.
Definition: AuthManager.php:2393
MediaWiki\Block\BlockManager
A service class for checking blocks.
Definition: BlockManager.php:46
Profiler\instance
static instance()
Singleton.
Definition: Profiler.php:69
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:172
MediaWiki\Auth\AuthManager\ACTION_UNLINK
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
Definition: AuthManager.php:117
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
MediaWiki\Auth\AuthManager\AUTOCREATE_SOURCE_SESSION
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
Definition: AuthManager.php:127
MediaWiki\Auth\AuthManager\getHookContainer
getHookContainer()
Definition: AuthManager.php:2501
MediaWiki\Auth\AuthManager\revokeAccessForUser
revokeAccessForUser( $username)
Revoke any authentication credentials for a user.
Definition: AuthManager.php:881
MediaWiki\Auth\AuthManager\$userNameUtils
UserNameUtils $userNameUtils
Definition: AuthManager.php:148
MediaWiki\Block\BlockErrorFormatter
A service class for getting formatted information about a block.
Definition: BlockErrorFormatter.php:35
ReadOnlyMode
A service class for fetching the wiki's current read-only mode.
Definition: ReadOnlyMode.php:11
MediaWiki\Auth\AuthManager\$instance
static AuthManager null $instance
Definition: AuthManager.php:133
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:119
MediaWiki\Auth\PrimaryAuthenticationProvider\TYPE_LINK
const TYPE_LINK
Provider can link to existing accounts elsewhere.
Definition: PrimaryAuthenticationProvider.php:79
MediaWiki\Auth\CreatedAccountAuthenticationRequest
Returned from account creation to allow for logging into the created account Stable to extend.
Definition: CreatedAccountAuthenticationRequest.php:30
MediaWiki\Auth\AuthManager\beginAuthentication
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
Definition: AuthManager.php:319
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:584
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1231
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3769
MediaWiki\Auth\AuthManager\userExists
userExists( $username, $flags=User::READ_NORMAL)
Determine whether a username exists.
Definition: AuthManager.php:2209
SpecialPage\getTitleFor
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,...
Definition: SpecialPage.php:107
MediaWiki\Auth\AuthManager\$allAuthenticationProviders
AuthenticationProvider[] $allAuthenticationProviders
Definition: AuthManager.php:151
MediaWiki\Auth\AuthManager\AUTOCREATE_SOURCE_MAINT
const AUTOCREATE_SOURCE_MAINT
Auto-creation is due to a Maintenance script.
Definition: AuthManager.php:130
MediaWiki\Auth\AuthManager\getPreAuthenticationProviders
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
Definition: AuthManager.php:2379
MediaWiki\Auth\AuthManager\getAuthenticationSessionData
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
Definition: AuthManager.php:2301
$res
$res
Definition: testCompression.php:57
MediaWiki\Auth\AuthManager\ACTION_LOGIN_CONTINUE
const ACTION_LOGIN_CONTINUE
Continue a login process that was interrupted by the need for user input or communication with an ext...
Definition: AuthManager.php:99
MediaWiki\Auth\AuthManager\$blockManager
BlockManager $blockManager
Definition: AuthManager.php:175
MediaWiki\Auth\AuthManager\SEC_FAIL
const SEC_FAIL
Security-sensitive should not be performed.
Definition: AuthManager.php:124
MediaWiki\Auth\AuthenticationRequest\getRequestByClass
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
Definition: AuthenticationRequest.php:274
MediaWiki\Auth\AuthManager\$hookContainer
HookContainer $hookContainer
Definition: AuthManager.php:166
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
MediaWiki\Auth\AuthManager\removeAuthenticationSessionData
removeAuthenticationSessionData( $key)
Remove authentication data.
Definition: AuthManager.php:2315
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:233
Config
Interface for configuration instances.
Definition: Config.php:30
MediaWiki\Auth\AuthenticationRequest\getUsernameFromRequests
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
Definition: AuthenticationRequest.php:294
User\addToDatabase
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3600
MediaWiki\Auth\AuthenticationRequest\PRIMARY_REQUIRED
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
Definition: AuthenticationRequest.php:53
MediaWiki\Auth\AuthenticationResponse\UI
const UI
Indicates that the authentication needs further user input of some sort.
Definition: AuthenticationResponse.php:55
MediaWiki\Auth\AuthManager\beginAccountCreation
beginAccountCreation(User $creator, array $reqs, $returnToUrl)
Start an account creation flow.
Definition: AuthManager.php:1082
MediaWiki\Auth\AuthManager\getAuthenticationProvider
getAuthenticationProvider( $id)
Get a provider by ID.
Definition: AuthManager.php:2249
MediaWiki\Auth\AuthManager\ACTION_LINK_CONTINUE
const ACTION_LINK_CONTINUE
Continue a user linking process that was interrupted by the need for user input or communication with...
Definition: AuthManager.php:111
MediaWiki\Auth\CreationReasonAuthenticationRequest
Authentication request for the reason given for account creation.
Definition: CreationReasonAuthenticationRequest.php:10
User\authorizeWrite
authorizeWrite(string $action, PageIdentity $target, PermissionStatus $status=null)
@unstable this is a part of the Authority experiment and should not be used yet.
Definition: User.php:4554
MediaWiki\Auth\AuthManager\canCreateAccounts
canCreateAccounts()
Determine whether accounts can be created.
Definition: AuthManager.php:964
MediaWiki\Auth\AuthenticationResponse\REDIRECT
const REDIRECT
Indicates that the authentication needs to be redirected to a third party to proceed.
Definition: AuthenticationResponse.php:58
MediaWiki\Auth\AuthManager\$readOnlyMode
ReadOnlyMode $readOnlyMode
Definition: AuthManager.php:172
MediaWiki\Auth\PreAuthenticationProvider
A pre-authentication provider can prevent authentication early on.
Definition: PreAuthenticationProvider.php:44
Status\wrap
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62
MediaWiki\Auth\CreateFromLoginAuthenticationRequest
This transfers state between the login and account creation flows.
Definition: CreateFromLoginAuthenticationRequest.php:35
MediaWiki\Auth\AuthManager\getConfiguration
getConfiguration()
Definition: AuthManager.php:2371
$args
if( $line===false) $args
Definition: mcc.php:124
MediaWiki\Auth\AuthenticationResponse\ABSTAIN
const ABSTAIN
Indicates that the authentication provider does not handle this request.
Definition: AuthenticationResponse.php:52
MediaWiki\Auth\AuthManager\canAuthenticateNow
canAuthenticateNow()
Indicate whether user authentication is possible.
Definition: AuthManager.php:297
MediaWiki\Auth\AuthManager\setAuthenticationSessionData
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
Definition: AuthManager.php:2284
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:71
MediaWiki\Auth\SecondaryAuthenticationProvider
A secondary provider mostly acts when the submitted authentication data has already been associated t...
Definition: SecondaryAuthenticationProvider.php:52
User\getId
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition: User.php:2067
MediaWiki\Auth\UsernameAuthenticationRequest
AuthenticationRequest to ensure something with a username is present Stable to extend.
Definition: UsernameAuthenticationRequest.php:30
User\setName
setName( $str)
Set the user name.
Definition: User.php:2136
MediaWiki\Auth\AuthManager\setDefaultUserOptions
setDefaultUserOptions(User $user, $useContextLang)
Definition: AuthManager.php:2447
MediaWiki\Auth\UserDataAuthenticationRequest
This represents additional user data requested on the account creation form.
Definition: UserDataAuthenticationRequest.php:35
MediaWiki\Auth\AuthenticationResponse\FAIL
const FAIL
Indicates that the authentication failed.
Definition: AuthenticationResponse.php:42
MediaWiki\Auth\AuthManager\resetCache
static resetCache()
Reset the internal caching for unit testing.
Definition: AuthManager.php:2488
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3417
MediaWiki\Auth\AuthManager\$request
WebRequest $request
Definition: AuthManager.php:136
MediaWiki\Auth\AuthManager\beginAccountLink
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
Definition: AuthManager.php:1838
MediaWiki\Auth\AuthManager\normalizeUsername
normalizeUsername( $username)
Provide normalized versions of the username for security checks.
Definition: AuthManager.php:857
MediaWiki\Auth\AuthManager\canLinkAccounts
canLinkAccounts()
Determine whether accounts can be linked.
Definition: AuthManager.php:1820
MediaWiki\Auth\AuthManager\ACTION_CREATE_CONTINUE
const ACTION_CREATE_CONTINUE
Continue a user creation process that was interrupted by the need for user input or communication wit...
Definition: AuthManager.php:105
MediaWiki\Auth\AuthManager\ACTION_CREATE
const ACTION_CREATE
Create a new user.
Definition: AuthManager.php:101
MediaWiki\Auth\AuthManager\$logger
LoggerInterface $logger
Definition: AuthManager.php:145
MediaWiki\Auth\AuthManager\$primaryAuthenticationProviders
PrimaryAuthenticationProvider[] $primaryAuthenticationProviders
Definition: AuthManager.php:157
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
SpecialPage
Parent class for all special pages.
Definition: SpecialPage.php:43
MediaWiki\Auth\AuthManager\continueAccountCreation
continueAccountCreation(array $reqs)
Continue an account creation flow.
Definition: AuthManager.php:1183
BotPassword\invalidateAllPasswordsForUser
static invalidateAllPasswordsForUser( $username)
Invalidate all passwords for a user, by name.
Definition: BotPassword.php:386
MediaWiki\Auth\AuthManager\$secondaryAuthenticationProviders
SecondaryAuthenticationProvider[] $secondaryAuthenticationProviders
Definition: AuthManager.php:160
MediaWiki\Auth\AuthManager\forcePrimaryAuthenticationProviders
forcePrimaryAuthenticationProviders(array $providers, $why)
Force certain PrimaryAuthenticationProviders.
Definition: AuthManager.php:241
MediaWiki\Auth\AuthManager\getHookRunner
getHookRunner()
Definition: AuthManager.php:2508
MediaWiki\Auth\AuthManager\ACTION_CHANGE
const ACTION_CHANGE
Change a user's credentials.
Definition: AuthManager.php:113
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3211
MediaWiki\Auth\AuthManager\callMethodOnProviders
callMethodOnProviders( $which, $method, array $args)
Definition: AuthManager.php:2467
MediaWiki\Auth\AuthManager\ACTION_LINK
const ACTION_LINK
Link an existing user to a third-party account.
Definition: AuthManager.php:107
MediaWiki\Auth\AuthManager\allowsPropertyChange
allowsPropertyChange( $property)
Determine whether a user property should be allowed to be changed.
Definition: AuthManager.php:2230
MediaWiki\Auth\RememberMeAuthenticationRequest
This is an authentication request added by AuthManager to show a "remember me" checkbox.
Definition: RememberMeAuthenticationRequest.php:34
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:476
MediaWiki\Auth\AuthManager\securitySensitiveOperationStatus
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
Definition: AuthManager.php:757
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:93
WebRequest
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:42
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:34
MediaWiki\Auth\AuthManager\$hookRunner
HookRunner $hookRunner
Definition: AuthManager.php:169
MediaWiki\Auth\AuthManager\ACTION_REMOVE
const ACTION_REMOVE
Remove a user's credentials.
Definition: AuthManager.php:115
MediaWiki\Auth\AuthManager\SEC_OK
const SEC_OK
Security-sensitive operations are ok.
Definition: AuthManager.php:120
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2099
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:44
MediaWiki\Auth\AuthManager\$objectFactory
ObjectFactory $objectFactory
Definition: AuthManager.php:142
MediaWiki\Auth\AuthManager\getAuthenticationRequests
getAuthenticationRequests( $action, User $user=null)
Return the applicable list of AuthenticationRequests.
Definition: AuthManager.php:2058
$cache
$cache
Definition: mcc.php:33
MediaWiki\Auth\AuthManager\singleton
static singleton()
Get the global AuthManager.
Definition: AuthManager.php:185
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2517
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:936
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
MediaWiki\Auth\AuthenticationResponse\newFail
static newFail(Message $msg)
Definition: AuthenticationResponse.php:146
MediaWiki\Auth\AuthManager\setSessionDataForUser
setSessionDataForUser( $user, $remember=null)
Log the user in.
Definition: AuthManager.php:2422
MediaWiki\Auth\AuthManager\$createdAccountAuthenticationRequests
CreatedAccountAuthenticationRequest[] $createdAccountAuthenticationRequests
Definition: AuthManager.php:163
MediaWiki\Auth\AuthManager\$config
Config $config
Definition: AuthManager.php:139
MediaWiki\Auth\AuthManager\canCreateAccount
canCreateAccount( $username, $options=[])
Determine whether a particular account can be created.
Definition: AuthManager.php:983
$source
$source
Definition: mwdoc-filter.php:34
MediaWiki\Auth\AuthManager\ACTION_LOGIN
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
Definition: AuthManager.php:95
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
MediaWiki\Auth\AuthManager\getSecondaryAuthenticationProviders
getSecondaryAuthenticationProviders()
Get the list of SecondaryAuthenticationProviders.
Definition: AuthManager.php:2407
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:575
MediaWiki\Auth\AuthManager\userCanAuthenticate
userCanAuthenticate( $username)
Determine whether a username can authenticate.
Definition: AuthManager.php:834
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:101
MediaWiki\Auth\AuthenticationResponse\PASS
const PASS
Indicates that the authentication succeeded.
Definition: AuthenticationResponse.php:39
MediaWiki\Auth\AuthManager\allowsAuthenticationDataChange
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
Definition: AuthManager.php:897
MediaWiki\Auth\AuthenticationResponse\newRestart
static newRestart(Message $msg)
Definition: AuthenticationResponse.php:159
User\isBlockedFromCreateAccount
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:3716
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:66
MediaWiki\Auth\PrimaryAuthenticationProvider
A primary authentication provider is responsible for associating the submitted authentication data wi...
Definition: PrimaryAuthenticationProvider.php:75
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
User\setOption
setOption( $oname, $val)
Set the given option for a user.
Definition: User.php:2718
MediaWiki\Auth
Definition: AbstractAuthenticationProvider.php:22
MediaWiki\Auth\AuthManager\setLogger
setLogger(LoggerInterface $logger)
Definition: AuthManager.php:224
MediaWiki\Auth\AuthManager\providerArrayFromSpecs
providerArrayFromSpecs( $class, array $specs)
Create an array of AuthenticationProviders from an array of ObjectFactory specs.
Definition: AuthManager.php:2334
MediaWiki\Auth\AuthenticationResponse\newPass
static newPass( $username=null)
Definition: AuthenticationResponse.php:134
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2108
MediaWiki\Auth\AuthManager\checkAccountCreatePermissions
checkAccountCreatePermissions(User $creator)
Basic permissions checks on whether a user can create accounts.
Definition: AuthManager.php:1031
MediaWiki\Auth\AuthenticationRequest
This is a value object for authentication requests.
Definition: AuthenticationRequest.php:38
MediaWiki\Auth\AuthenticationRequest\REQUIRED
const REQUIRED
Indicates that the request is required for authentication to proceed.
Definition: AuthenticationRequest.php:47
MediaWiki\Auth\AuthManager\$blockErrorFormatter
BlockErrorFormatter $blockErrorFormatter
Definition: AuthManager.php:178
MediaWiki\Auth\AuthManager\getAuthenticationRequestsInternal
getAuthenticationRequestsInternal( $providerAction, array $options, array $providers, User $user=null)
Internal request lookup for self::getAuthenticationRequests.
Definition: AuthManager.php:2128
MediaWiki\Auth\AuthenticationProvider
An AuthenticationProvider is used by AuthManager when authenticating users.
Definition: AuthenticationProvider.php:40