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