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 Status;
39 use StatusValue;
40 use User;
41 use WebRequest;
42 use Wikimedia\ObjectFactory;
43 
92 class AuthManager implements LoggerAwareInterface {
94  public const ACTION_LOGIN = 'login';
98  public const ACTION_LOGIN_CONTINUE = 'login-continue';
100  public const ACTION_CREATE = 'create';
104  public const ACTION_CREATE_CONTINUE = 'create-continue';
106  public const ACTION_LINK = 'link';
110  public const ACTION_LINK_CONTINUE = 'link-continue';
112  public const ACTION_CHANGE = 'change';
114  public const ACTION_REMOVE = 'remove';
116  public const ACTION_UNLINK = 'unlink';
117 
119  public const SEC_OK = 'ok';
121  public const SEC_REAUTH = 'reauth';
123  public const SEC_FAIL = 'fail';
124 
126  public const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
127 
129  public const AUTOCREATE_SOURCE_MAINT = '::Maintenance::';
130 
132  private static $instance = null;
133 
135  private $request;
136 
138  private $config;
139 
141  private $objectFactory;
142 
144  private $logger;
145 
147  private $permManager;
148 
150  private $userNameUtils;
151 
154 
157 
160 
163 
166 
168  private $hookContainer;
169 
171  private $hookRunner;
172 
174  private $readOnlyMode;
175 
177  private $blockManager;
178 
181 
187  public static function singleton() {
188  return MediaWikiServices::getInstance()->getAuthManager();
189  }
190 
202  public function __construct(
204  Config $config,
205  ObjectFactory $objectFactory,
212  ) {
213  $this->request = $request;
214  $this->config = $config;
215  $this->objectFactory = $objectFactory;
216  $this->permManager = $permManager;
217  $this->hookContainer = $hookContainer;
218  $this->hookRunner = new HookRunner( $hookContainer );
219  $this->setLogger( new NullLogger() );
220  $this->readOnlyMode = $readOnlyMode;
221  $this->userNameUtils = $userNameUtils;
222  $this->blockManager = $blockManager;
223  $this->blockErrorFormatter = $blockErrorFormatter;
224  }
225 
229  public function setLogger( LoggerInterface $logger ) {
230  $this->logger = $logger;
231  }
232 
236  public function getRequest() {
237  return $this->request;
238  }
239 
246  public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
247  $this->logger->warning( "Overriding AuthManager primary authn because $why" );
248 
249  if ( $this->primaryAuthenticationProviders !== null ) {
250  $this->logger->warning(
251  'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
252  );
253 
254  $this->allAuthenticationProviders = array_diff_key(
255  $this->allAuthenticationProviders,
256  $this->primaryAuthenticationProviders
257  );
258  $session = $this->request->getSession();
259  $session->remove( 'AuthManager::authnState' );
260  $session->remove( 'AuthManager::accountCreationState' );
261  $session->remove( 'AuthManager::accountLinkState' );
262  $this->createdAccountAuthenticationRequests = [];
263  }
264 
265  $this->primaryAuthenticationProviders = [];
266  foreach ( $providers as $provider ) {
267  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
268  throw new \RuntimeException(
269  'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
270  get_class( $provider )
271  );
272  }
273  $provider->setLogger( $this->logger );
274  $provider->setManager( $this );
275  $provider->setConfig( $this->config );
276  $provider->setHookContainer( $this->hookContainer );
277  $id = $provider->getUniqueId();
278  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
279  throw new \RuntimeException(
280  "Duplicate specifications for id $id (classes " .
281  get_class( $provider ) . ' and ' .
282  get_class( $this->allAuthenticationProviders[$id] ) . ')'
283  );
284  }
285  $this->allAuthenticationProviders[$id] = $provider;
286  $this->primaryAuthenticationProviders[$id] = $provider;
287  }
288  }
289 
303  public function canAuthenticateNow() {
304  return $this->request->getSession()->canSetUser();
305  }
306 
325  public function beginAuthentication( array $reqs, $returnToUrl ) {
326  $session = $this->request->getSession();
327  if ( !$session->canSetUser() ) {
328  // Caller should have called canAuthenticateNow()
329  $session->remove( 'AuthManager::authnState' );
330  throw new \LogicException( 'Authentication is not possible now' );
331  }
332 
333  $guessUserName = null;
334  foreach ( $reqs as $req ) {
335  $req->returnToUrl = $returnToUrl;
336  // @codeCoverageIgnoreStart
337  if ( $req->username !== null && $req->username !== '' ) {
338  if ( $guessUserName === null ) {
339  $guessUserName = $req->username;
340  } elseif ( $guessUserName !== $req->username ) {
341  $guessUserName = null;
342  break;
343  }
344  }
345  // @codeCoverageIgnoreEnd
346  }
347 
348  // Check for special-case login of a just-created account
350  $reqs, CreatedAccountAuthenticationRequest::class
351  );
352  if ( $req ) {
353  if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
354  throw new \LogicException(
355  'CreatedAccountAuthenticationRequests are only valid on ' .
356  'the same AuthManager that created the account'
357  );
358  }
359 
360  $user = User::newFromName( $req->username );
361  // @codeCoverageIgnoreStart
362  if ( !$user ) {
363  throw new \UnexpectedValueException(
364  "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
365  );
366  } elseif ( $user->getId() != $req->id ) {
367  throw new \UnexpectedValueException(
368  "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
369  );
370  }
371  // @codeCoverageIgnoreEnd
372 
373  $this->logger->info( 'Logging in {user} after account creation', [
374  'user' => $user->getName(),
375  ] );
376  $ret = AuthenticationResponse::newPass( $user->getName() );
377  $this->setSessionDataForUser( $user );
378  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
379  $session->remove( 'AuthManager::authnState' );
380  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
381  $ret, $user, $user->getName(), [] );
382  return $ret;
383  }
384 
385  $this->removeAuthenticationSessionData( null );
386 
387  foreach ( $this->getPreAuthenticationProviders() as $provider ) {
388  $status = $provider->testForAuthentication( $reqs );
389  if ( !$status->isGood() ) {
390  $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
392  Status::wrap( $status )->getMessage()
393  );
394  $this->callMethodOnProviders( 7, 'postAuthentication',
395  [ User::newFromName( $guessUserName ) ?: null, $ret ]
396  );
397  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit( $ret, null, $guessUserName, [] );
398  return $ret;
399  }
400  }
401 
402  $state = [
403  'reqs' => $reqs,
404  'returnToUrl' => $returnToUrl,
405  'guessUserName' => $guessUserName,
406  'primary' => null,
407  'primaryResponse' => null,
408  'secondary' => [],
409  'maybeLink' => [],
410  'continueRequests' => [],
411  ];
412 
413  // Preserve state from a previous failed login
415  $reqs, CreateFromLoginAuthenticationRequest::class
416  );
417  if ( $req ) {
418  $state['maybeLink'] = $req->maybeLink;
419  }
420 
421  $session = $this->request->getSession();
422  $session->setSecret( 'AuthManager::authnState', $state );
423  $session->persist();
424 
425  return $this->continueAuthentication( $reqs );
426  }
427 
450  public function continueAuthentication( array $reqs ) {
451  $session = $this->request->getSession();
452  try {
453  if ( !$session->canSetUser() ) {
454  // Caller should have called canAuthenticateNow()
455  // @codeCoverageIgnoreStart
456  throw new \LogicException( 'Authentication is not possible now' );
457  // @codeCoverageIgnoreEnd
458  }
459 
460  $state = $session->getSecret( 'AuthManager::authnState' );
461  if ( !is_array( $state ) ) {
463  wfMessage( 'authmanager-authn-not-in-progress' )
464  );
465  }
466  $state['continueRequests'] = [];
467 
468  $guessUserName = $state['guessUserName'];
469 
470  foreach ( $reqs as $req ) {
471  $req->returnToUrl = $state['returnToUrl'];
472  }
473 
474  // Step 1: Choose an primary authentication provider, and call it until it succeeds.
475 
476  if ( $state['primary'] === null ) {
477  // We haven't picked a PrimaryAuthenticationProvider yet
478  // @codeCoverageIgnoreStart
479  $guessUserName = null;
480  foreach ( $reqs as $req ) {
481  if ( $req->username !== null && $req->username !== '' ) {
482  if ( $guessUserName === null ) {
483  $guessUserName = $req->username;
484  } elseif ( $guessUserName !== $req->username ) {
485  $guessUserName = null;
486  break;
487  }
488  }
489  }
490  $state['guessUserName'] = $guessUserName;
491  // @codeCoverageIgnoreEnd
492  $state['reqs'] = $reqs;
493 
494  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
495  $res = $provider->beginPrimaryAuthentication( $reqs );
496  switch ( $res->status ) {
498  $state['primary'] = $id;
499  $state['primaryResponse'] = $res;
500  $this->logger->debug( "Primary login with $id succeeded" );
501  break 2;
503  $this->logger->debug( "Login failed in primary authentication by $id" );
504  if ( $res->createRequest || $state['maybeLink'] ) {
505  $res->createRequest = new CreateFromLoginAuthenticationRequest(
506  $res->createRequest, $state['maybeLink']
507  );
508  }
509  $this->callMethodOnProviders( 7, 'postAuthentication',
510  [ User::newFromName( $guessUserName ) ?: null, $res ]
511  );
512  $session->remove( 'AuthManager::authnState' );
513  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
514  $res, null, $guessUserName, [] );
515  return $res;
517  // Continue loop
518  break;
521  $this->logger->debug( "Primary login with $id returned $res->status" );
522  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
523  $state['primary'] = $id;
524  $state['continueRequests'] = $res->neededRequests;
525  $session->setSecret( 'AuthManager::authnState', $state );
526  return $res;
527 
528  // @codeCoverageIgnoreStart
529  default:
530  throw new \DomainException(
531  get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
532  );
533  // @codeCoverageIgnoreEnd
534  }
535  }
536  if ( $state['primary'] === null ) {
537  $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
539  wfMessage( 'authmanager-authn-no-primary' )
540  );
541  $this->callMethodOnProviders( 7, 'postAuthentication',
542  [ User::newFromName( $guessUserName ) ?: null, $ret ]
543  );
544  $session->remove( 'AuthManager::authnState' );
545  return $ret;
546  }
547  } elseif ( $state['primaryResponse'] === null ) {
548  $provider = $this->getAuthenticationProvider( $state['primary'] );
549  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
550  // Configuration changed? Force them to start over.
551  // @codeCoverageIgnoreStart
553  wfMessage( 'authmanager-authn-not-in-progress' )
554  );
555  $this->callMethodOnProviders( 7, 'postAuthentication',
556  [ User::newFromName( $guessUserName ) ?: null, $ret ]
557  );
558  $session->remove( 'AuthManager::authnState' );
559  return $ret;
560  // @codeCoverageIgnoreEnd
561  }
562  $id = $provider->getUniqueId();
563  $res = $provider->continuePrimaryAuthentication( $reqs );
564  switch ( $res->status ) {
566  $state['primaryResponse'] = $res;
567  $this->logger->debug( "Primary login with $id succeeded" );
568  break;
570  $this->logger->debug( "Login failed in primary authentication by $id" );
571  if ( $res->createRequest || $state['maybeLink'] ) {
572  $res->createRequest = new CreateFromLoginAuthenticationRequest(
573  $res->createRequest, $state['maybeLink']
574  );
575  }
576  $this->callMethodOnProviders( 7, 'postAuthentication',
577  [ User::newFromName( $guessUserName ) ?: null, $res ]
578  );
579  $session->remove( 'AuthManager::authnState' );
580  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
581  $res, null, $guessUserName, [] );
582  return $res;
585  $this->logger->debug( "Primary login with $id returned $res->status" );
586  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
587  $state['continueRequests'] = $res->neededRequests;
588  $session->setSecret( 'AuthManager::authnState', $state );
589  return $res;
590  default:
591  throw new \DomainException(
592  get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
593  );
594  }
595  }
596 
597  $res = $state['primaryResponse'];
598  if ( $res->username === null ) {
599  $provider = $this->getAuthenticationProvider( $state['primary'] );
600  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
601  // Configuration changed? Force them to start over.
602  // @codeCoverageIgnoreStart
604  wfMessage( 'authmanager-authn-not-in-progress' )
605  );
606  $this->callMethodOnProviders( 7, 'postAuthentication',
607  [ User::newFromName( $guessUserName ) ?: null, $ret ]
608  );
609  $session->remove( 'AuthManager::authnState' );
610  return $ret;
611  // @codeCoverageIgnoreEnd
612  }
613 
614  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
615  $res->linkRequest &&
616  // don't confuse the user with an incorrect message if linking is disabled
617  $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
618  ) {
619  $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
620  $msg = 'authmanager-authn-no-local-user-link';
621  } else {
622  $msg = 'authmanager-authn-no-local-user';
623  }
624  $this->logger->debug(
625  "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
626  );
628  $ret->neededRequests = $this->getAuthenticationRequestsInternal(
629  self::ACTION_LOGIN,
630  [],
632  );
633  if ( $res->createRequest || $state['maybeLink'] ) {
634  $ret->createRequest = new CreateFromLoginAuthenticationRequest(
635  $res->createRequest, $state['maybeLink']
636  );
637  $ret->neededRequests[] = $ret->createRequest;
638  }
639  $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
640  $session->setSecret( 'AuthManager::authnState', [
641  'reqs' => [], // Will be filled in later
642  'primary' => null,
643  'primaryResponse' => null,
644  'secondary' => [],
645  'continueRequests' => $ret->neededRequests,
646  ] + $state );
647  return $ret;
648  }
649 
650  // Step 2: Primary authentication succeeded, create the User object
651  // (and add the user locally if necessary)
652 
653  $user = User::newFromName( $res->username, 'usable' );
654  if ( !$user ) {
655  $provider = $this->getAuthenticationProvider( $state['primary'] );
656  throw new \DomainException(
657  get_class( $provider ) . " returned an invalid username: {$res->username}"
658  );
659  }
660  if ( $user->getId() === 0 ) {
661  // User doesn't exist locally. Create it.
662  $this->logger->info( 'Auto-creating {user} on login', [
663  'user' => $user->getName(),
664  ] );
665  $status = $this->autoCreateUser( $user, $state['primary'], false );
666  if ( !$status->isGood() ) {
668  Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
669  );
670  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
671  $session->remove( 'AuthManager::authnState' );
672  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
673  $ret, $user, $user->getName(), [] );
674  return $ret;
675  }
676  }
677 
678  // Step 3: Iterate over all the secondary authentication providers.
679 
680  $beginReqs = $state['reqs'];
681 
682  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
683  if ( !isset( $state['secondary'][$id] ) ) {
684  // This provider isn't started yet, so we pass it the set
685  // of reqs from beginAuthentication instead of whatever
686  // might have been used by a previous provider in line.
687  $func = 'beginSecondaryAuthentication';
688  $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
689  } elseif ( !$state['secondary'][$id] ) {
690  $func = 'continueSecondaryAuthentication';
691  $res = $provider->continueSecondaryAuthentication( $user, $reqs );
692  } else {
693  continue;
694  }
695  switch ( $res->status ) {
697  $this->logger->debug( "Secondary login with $id succeeded" );
698  // fall through
700  $state['secondary'][$id] = true;
701  break;
703  $this->logger->debug( "Login failed in secondary authentication by $id" );
704  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
705  $session->remove( 'AuthManager::authnState' );
706  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
707  $res, $user, $user->getName(), [] );
708  return $res;
711  $this->logger->debug( "Secondary login with $id returned " . $res->status );
712  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
713  $state['secondary'][$id] = false;
714  $state['continueRequests'] = $res->neededRequests;
715  $session->setSecret( 'AuthManager::authnState', $state );
716  return $res;
717 
718  // @codeCoverageIgnoreStart
719  default:
720  throw new \DomainException(
721  get_class( $provider ) . "::{$func}() returned $res->status"
722  );
723  // @codeCoverageIgnoreEnd
724  }
725  }
726 
727  // Step 4: Authentication complete! Set the user in the session and
728  // clean up.
729 
730  $this->logger->info( 'Login for {user} succeeded from {clientip}', [
731  'user' => $user->getName(),
732  'clientip' => $this->request->getIP(),
733  ] );
736  $beginReqs, RememberMeAuthenticationRequest::class
737  );
738  $this->setSessionDataForUser( $user, $req && $req->rememberMe );
739  $ret = AuthenticationResponse::newPass( $user->getName() );
740  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
741  $session->remove( 'AuthManager::authnState' );
742  $this->removeAuthenticationSessionData( null );
743  $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
744  $ret, $user, $user->getName(), [] );
745  return $ret;
746  } catch ( \Exception $ex ) {
747  $session->remove( 'AuthManager::authnState' );
748  throw $ex;
749  }
750  }
751 
763  public function securitySensitiveOperationStatus( $operation ) {
764  $status = self::SEC_OK;
765 
766  $this->logger->debug( __METHOD__ . ": Checking $operation" );
767 
768  $session = $this->request->getSession();
769  $aId = $session->getUser()->getId();
770  if ( $aId === 0 ) {
771  // User isn't authenticated. DWIM?
772  $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
773  $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
774  return $status;
775  }
776 
777  if ( $session->canSetUser() ) {
778  $id = $session->get( 'AuthManager:lastAuthId' );
779  $last = $session->get( 'AuthManager:lastAuthTimestamp' );
780  if ( $id !== $aId || $last === null ) {
781  $timeSinceLogin = PHP_INT_MAX; // Forever ago
782  } else {
783  $timeSinceLogin = max( 0, time() - $last );
784  }
785 
786  $thresholds = $this->config->get( 'ReauthenticateTime' );
787  if ( isset( $thresholds[$operation] ) ) {
788  $threshold = $thresholds[$operation];
789  } elseif ( isset( $thresholds['default'] ) ) {
790  $threshold = $thresholds['default'];
791  } else {
792  throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
793  }
794 
795  if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
796  $status = self::SEC_REAUTH;
797  }
798  } else {
799  $timeSinceLogin = -1;
800 
801  $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
802  if ( isset( $pass[$operation] ) ) {
803  $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
804  } elseif ( isset( $pass['default'] ) ) {
805  $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
806  } else {
807  throw new \UnexpectedValueException(
808  '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
809  );
810  }
811  }
812 
813  $this->getHookRunner()->onSecuritySensitiveOperationStatus(
814  $status, $operation, $session, $timeSinceLogin );
815 
816  // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
817  if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
818  $status = self::SEC_FAIL;
819  }
820 
821  $this->logger->info( __METHOD__ . ": $operation is $status for '{user}'",
822  [
823  'user' => $session->getUser()->getName(),
824  'clientip' => $this->getRequest()->getIP(),
825  ]
826  );
827 
828  return $status;
829  }
830 
840  public function userCanAuthenticate( $username ) {
841  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
842  if ( $provider->testUserCanAuthenticate( $username ) ) {
843  return true;
844  }
845  }
846  return false;
847  }
848 
863  public function normalizeUsername( $username ) {
864  $ret = [];
865  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
866  $normalized = $provider->providerNormalizeUsername( $username );
867  if ( $normalized !== null ) {
868  $ret[$normalized] = true;
869  }
870  }
871  return array_keys( $ret );
872  }
873 
888  public function revokeAccessForUser( $username ) {
889  $this->logger->info( 'Revoking access for {user}', [
890  'user' => $username,
891  ] );
892  $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
893  }
894 
904  public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
905  $any = false;
906  $providers = $this->getPrimaryAuthenticationProviders() +
908 
909  foreach ( $providers as $provider ) {
910  $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
911  if ( !$status->isGood() ) {
912  // If status is not good because reset email password last attempt was within
913  // $wgPasswordReminderResendTime then return good status with throttled-mailpassword value;
914  // otherwise, return the $status wrapped.
915  return $status->hasMessage( 'throttled-mailpassword' )
916  ? Status::newGood( 'throttled-mailpassword' )
917  : Status::wrap( $status );
918  }
919  $any = $any || $status->value !== 'ignored';
920  }
921  if ( !$any ) {
922  $status = Status::newGood( 'ignored' );
923  $status->warning( 'authmanager-change-not-supported' );
924  return $status;
925  }
926  return Status::newGood();
927  }
928 
946  public function changeAuthenticationData( AuthenticationRequest $req, $isAddition = false ) {
947  $this->logger->info( 'Changing authentication data for {user} class {what}', [
948  'user' => is_string( $req->username ) ? $req->username : '<no name>',
949  'what' => get_class( $req ),
950  ] );
951 
952  $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
953 
954  // When the main account's authentication data is changed, invalidate
955  // all BotPasswords too.
956  if ( !$isAddition ) {
958  }
959  }
960 
972  public function canCreateAccounts() {
973  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
974  switch ( $provider->accountCreationType() ) {
977  return true;
978  }
979  }
980  return false;
981  }
982 
991  public function canCreateAccount( $username, $options = [] ) {
992  // Back compat
993  if ( is_int( $options ) ) {
994  $options = [ 'flags' => $options ];
995  }
996  $options += [
997  'flags' => User::READ_NORMAL,
998  'creating' => false,
999  ];
1000  $flags = $options['flags'];
1001 
1002  if ( !$this->canCreateAccounts() ) {
1003  return Status::newFatal( 'authmanager-create-disabled' );
1004  }
1005 
1006  if ( $this->userExists( $username, $flags ) ) {
1007  return Status::newFatal( 'userexists' );
1008  }
1009 
1010  $user = User::newFromName( $username, 'creatable' );
1011  if ( !is_object( $user ) ) {
1012  return Status::newFatal( 'noname' );
1013  } else {
1014  $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
1015  if ( $user->getId() !== 0 ) {
1016  return Status::newFatal( 'userexists' );
1017  }
1018  }
1019 
1020  // Denied by providers?
1021  $providers = $this->getPreAuthenticationProviders() +
1024  foreach ( $providers as $provider ) {
1025  $status = $provider->testUserForCreation( $user, false, $options );
1026  if ( !$status->isGood() ) {
1027  return Status::wrap( $status );
1028  }
1029  }
1030 
1031  return Status::newGood();
1032  }
1033 
1039  public function checkAccountCreatePermissions( User $creator ) {
1040  // Wiki is read-only?
1041  if ( $this->readOnlyMode->isReadOnly() ) {
1042  return Status::newFatal( wfMessage( 'readonlytext', $this->readOnlyMode->getReason() ) );
1043  }
1044 
1045  $permErrors = $this->permManager->getPermissionErrors(
1046  'createaccount',
1047  $creator,
1048  \SpecialPage::getTitleFor( 'CreateAccount' )
1049  );
1050  if ( $permErrors ) {
1051  $status = Status::newGood();
1052  foreach ( $permErrors as $args ) {
1053  $status->fatal( ...$args );
1054  }
1055  return $status;
1056  }
1057 
1058  $ip = $this->getRequest()->getIP();
1059 
1060  $block = $creator->isBlockedFromCreateAccount();
1061  if ( $block ) {
1062  $language = \RequestContext::getMain()->getLanguage();
1063  return Status::newFatal(
1064  $this->blockErrorFormatter->getMessage( $block, $creator, $language, $ip )
1065  );
1066  }
1067 
1068  if ( $this->blockManager->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
1069  return Status::newFatal( 'sorbs_create_account_reason' );
1070  }
1071 
1072  return Status::newGood();
1073  }
1074 
1094  public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
1095  $session = $this->request->getSession();
1096  if ( !$this->canCreateAccounts() ) {
1097  // Caller should have called canCreateAccounts()
1098  $session->remove( 'AuthManager::accountCreationState' );
1099  throw new \LogicException( 'Account creation is not possible' );
1100  }
1101 
1102  try {
1104  } catch ( \UnexpectedValueException $ex ) {
1105  $username = null;
1106  }
1107  if ( $username === null ) {
1108  $this->logger->debug( __METHOD__ . ': No username provided' );
1109  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1110  }
1111 
1112  // Permissions check
1113  $status = $this->checkAccountCreatePermissions( $creator );
1114  if ( !$status->isGood() ) {
1115  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {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  $status = $this->canCreateAccount(
1124  $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1125  );
1126  if ( !$status->isGood() ) {
1127  $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1128  'user' => $username,
1129  'creator' => $creator->getName(),
1130  'reason' => $status->getWikiText( null, null, 'en' )
1131  ] );
1132  return AuthenticationResponse::newFail( $status->getMessage() );
1133  }
1134 
1135  $user = User::newFromName( $username, 'creatable' );
1136  foreach ( $reqs as $req ) {
1137  $req->username = $username;
1138  $req->returnToUrl = $returnToUrl;
1139  if ( $req instanceof UserDataAuthenticationRequest ) {
1140  $status = $req->populateUser( $user );
1141  if ( !$status->isGood() ) {
1142  $status = Status::wrap( $status );
1143  $session->remove( 'AuthManager::accountCreationState' );
1144  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1145  'user' => $user->getName(),
1146  'creator' => $creator->getName(),
1147  'reason' => $status->getWikiText( null, null, 'en' ),
1148  ] );
1149  return AuthenticationResponse::newFail( $status->getMessage() );
1150  }
1151  }
1152  }
1153 
1154  $this->removeAuthenticationSessionData( null );
1155 
1156  $state = [
1157  'username' => $username,
1158  'userid' => 0,
1159  'creatorid' => $creator->getId(),
1160  'creatorname' => $creator->getName(),
1161  'reqs' => $reqs,
1162  'returnToUrl' => $returnToUrl,
1163  'primary' => null,
1164  'primaryResponse' => null,
1165  'secondary' => [],
1166  'continueRequests' => [],
1167  'maybeLink' => [],
1168  'ranPreTests' => false,
1169  ];
1170 
1171  // Special case: converting a login to an account creation
1173  $reqs, CreateFromLoginAuthenticationRequest::class
1174  );
1175  if ( $req ) {
1176  $state['maybeLink'] = $req->maybeLink;
1177 
1178  if ( $req->createRequest ) {
1179  $reqs[] = $req->createRequest;
1180  $state['reqs'][] = $req->createRequest;
1181  }
1182  }
1183 
1184  $session->setSecret( 'AuthManager::accountCreationState', $state );
1185  $session->persist();
1186 
1187  return $this->continueAccountCreation( $reqs );
1188  }
1189 
1195  public function continueAccountCreation( array $reqs ) {
1196  $session = $this->request->getSession();
1197  try {
1198  if ( !$this->canCreateAccounts() ) {
1199  // Caller should have called canCreateAccounts()
1200  $session->remove( 'AuthManager::accountCreationState' );
1201  throw new \LogicException( 'Account creation is not possible' );
1202  }
1203 
1204  $state = $session->getSecret( 'AuthManager::accountCreationState' );
1205  if ( !is_array( $state ) ) {
1207  wfMessage( 'authmanager-create-not-in-progress' )
1208  );
1209  }
1210  $state['continueRequests'] = [];
1211 
1212  // Step 0: Prepare and validate the input
1213 
1214  $user = User::newFromName( $state['username'], 'creatable' );
1215  if ( !is_object( $user ) ) {
1216  $session->remove( 'AuthManager::accountCreationState' );
1217  $this->logger->debug( __METHOD__ . ': Invalid username', [
1218  'user' => $state['username'],
1219  ] );
1220  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1221  }
1222 
1223  if ( $state['creatorid'] ) {
1224  $creator = User::newFromId( $state['creatorid'] );
1225  } else {
1226  $creator = new User;
1227  $creator->setName( $state['creatorname'] );
1228  }
1229 
1230  // Avoid account creation races on double submissions
1232  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1233  if ( !$lock ) {
1234  // Don't clear AuthManager::accountCreationState for this code
1235  // path because the process that won the race owns it.
1236  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1237  'user' => $user->getName(),
1238  'creator' => $creator->getName(),
1239  ] );
1240  return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1241  }
1242 
1243  // Permissions check
1244  $status = $this->checkAccountCreatePermissions( $creator );
1245  if ( !$status->isGood() ) {
1246  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1247  'user' => $user->getName(),
1248  'creator' => $creator->getName(),
1249  'reason' => $status->getWikiText( null, null, 'en' )
1250  ] );
1251  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1252  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1253  $session->remove( 'AuthManager::accountCreationState' );
1254  return $ret;
1255  }
1256 
1257  // Load from master for existence check
1258  $user->load( User::READ_LOCKING );
1259 
1260  if ( $state['userid'] === 0 ) {
1261  if ( $user->getId() !== 0 ) {
1262  $this->logger->debug( __METHOD__ . ': User exists locally', [
1263  'user' => $user->getName(),
1264  'creator' => $creator->getName(),
1265  ] );
1266  $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1267  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1268  $session->remove( 'AuthManager::accountCreationState' );
1269  return $ret;
1270  }
1271  } else {
1272  if ( $user->getId() === 0 ) {
1273  $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1274  'user' => $user->getName(),
1275  'creator' => $creator->getName(),
1276  'expected_id' => $state['userid'],
1277  ] );
1278  throw new \UnexpectedValueException(
1279  "User \"{$state['username']}\" should exist now, but doesn't!"
1280  );
1281  }
1282  if ( $user->getId() !== $state['userid'] ) {
1283  $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1284  'user' => $user->getName(),
1285  'creator' => $creator->getName(),
1286  'expected_id' => $state['userid'],
1287  'actual_id' => $user->getId(),
1288  ] );
1289  throw new \UnexpectedValueException(
1290  "User \"{$state['username']}\" exists, but " .
1291  "ID {$user->getId()} !== {$state['userid']}!"
1292  );
1293  }
1294  }
1295  foreach ( $state['reqs'] as $req ) {
1296  if ( $req instanceof UserDataAuthenticationRequest ) {
1297  $status = $req->populateUser( $user );
1298  if ( !$status->isGood() ) {
1299  // This should never happen...
1300  $status = Status::wrap( $status );
1301  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1302  'user' => $user->getName(),
1303  'creator' => $creator->getName(),
1304  'reason' => $status->getWikiText( null, null, 'en' ),
1305  ] );
1306  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1307  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1308  $session->remove( 'AuthManager::accountCreationState' );
1309  return $ret;
1310  }
1311  }
1312  }
1313 
1314  foreach ( $reqs as $req ) {
1315  $req->returnToUrl = $state['returnToUrl'];
1316  $req->username = $state['username'];
1317  }
1318 
1319  // Run pre-creation tests, if we haven't already
1320  if ( !$state['ranPreTests'] ) {
1321  $providers = $this->getPreAuthenticationProviders() +
1324  foreach ( $providers as $id => $provider ) {
1325  $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1326  if ( !$status->isGood() ) {
1327  $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1328  'user' => $user->getName(),
1329  'creator' => $creator->getName(),
1330  ] );
1332  Status::wrap( $status )->getMessage()
1333  );
1334  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1335  $session->remove( 'AuthManager::accountCreationState' );
1336  return $ret;
1337  }
1338  }
1339 
1340  $state['ranPreTests'] = true;
1341  }
1342 
1343  // Step 1: Choose a primary authentication provider and call it until it succeeds.
1344 
1345  if ( $state['primary'] === null ) {
1346  // We haven't picked a PrimaryAuthenticationProvider yet
1347  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1348  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1349  continue;
1350  }
1351  $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1352  switch ( $res->status ) {
1354  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1355  'user' => $user->getName(),
1356  'creator' => $creator->getName(),
1357  ] );
1358  $state['primary'] = $id;
1359  $state['primaryResponse'] = $res;
1360  break 2;
1362  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1363  'user' => $user->getName(),
1364  'creator' => $creator->getName(),
1365  ] );
1366  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1367  $session->remove( 'AuthManager::accountCreationState' );
1368  return $res;
1370  // Continue loop
1371  break;
1374  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1375  'user' => $user->getName(),
1376  'creator' => $creator->getName(),
1377  ] );
1378  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1379  $state['primary'] = $id;
1380  $state['continueRequests'] = $res->neededRequests;
1381  $session->setSecret( 'AuthManager::accountCreationState', $state );
1382  return $res;
1383 
1384  // @codeCoverageIgnoreStart
1385  default:
1386  throw new \DomainException(
1387  get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1388  );
1389  // @codeCoverageIgnoreEnd
1390  }
1391  }
1392  if ( $state['primary'] === null ) {
1393  $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1394  'user' => $user->getName(),
1395  'creator' => $creator->getName(),
1396  ] );
1398  wfMessage( 'authmanager-create-no-primary' )
1399  );
1400  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1401  $session->remove( 'AuthManager::accountCreationState' );
1402  return $ret;
1403  }
1404  } elseif ( $state['primaryResponse'] === null ) {
1405  $provider = $this->getAuthenticationProvider( $state['primary'] );
1406  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1407  // Configuration changed? Force them to start over.
1408  // @codeCoverageIgnoreStart
1410  wfMessage( 'authmanager-create-not-in-progress' )
1411  );
1412  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1413  $session->remove( 'AuthManager::accountCreationState' );
1414  return $ret;
1415  // @codeCoverageIgnoreEnd
1416  }
1417  $id = $provider->getUniqueId();
1418  $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1419  switch ( $res->status ) {
1421  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1422  'user' => $user->getName(),
1423  'creator' => $creator->getName(),
1424  ] );
1425  $state['primaryResponse'] = $res;
1426  break;
1428  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1429  'user' => $user->getName(),
1430  'creator' => $creator->getName(),
1431  ] );
1432  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1433  $session->remove( 'AuthManager::accountCreationState' );
1434  return $res;
1437  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1438  'user' => $user->getName(),
1439  'creator' => $creator->getName(),
1440  ] );
1441  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1442  $state['continueRequests'] = $res->neededRequests;
1443  $session->setSecret( 'AuthManager::accountCreationState', $state );
1444  return $res;
1445  default:
1446  throw new \DomainException(
1447  get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1448  );
1449  }
1450  }
1451 
1452  // Step 2: Primary authentication succeeded, create the User object
1453  // and add the user locally.
1454 
1455  if ( $state['userid'] === 0 ) {
1456  $this->logger->info( 'Creating user {user} during account creation', [
1457  'user' => $user->getName(),
1458  'creator' => $creator->getName(),
1459  ] );
1460  $status = $user->addToDatabase();
1461  if ( !$status->isOK() ) {
1462  // @codeCoverageIgnoreStart
1463  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1464  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1465  $session->remove( 'AuthManager::accountCreationState' );
1466  return $ret;
1467  // @codeCoverageIgnoreEnd
1468  }
1469  $this->setDefaultUserOptions( $user, $creator->isAnon() );
1470  $this->getHookRunner()->onLocalUserCreated( $user, false );
1471  $user->saveSettings();
1472  $state['userid'] = $user->getId();
1473 
1474  // Update user count
1475  \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1476 
1477  // Watch user's userpage and talk page
1478  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1479 
1480  // Inform the provider
1481  $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1482 
1483  // Log the creation
1484  if ( $this->config->get( 'NewUserLog' ) ) {
1485  $isAnon = $creator->isAnon();
1486  $logEntry = new \ManualLogEntry(
1487  'newusers',
1488  $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1489  );
1490  $logEntry->setPerformer( $isAnon ? $user : $creator );
1491  $logEntry->setTarget( $user->getUserPage() );
1494  $state['reqs'], CreationReasonAuthenticationRequest::class
1495  );
1496  $logEntry->setComment( $req ? $req->reason : '' );
1497  $logEntry->setParameters( [
1498  '4::userid' => $user->getId(),
1499  ] );
1500  $logid = $logEntry->insert();
1501  $logEntry->publish( $logid );
1502  }
1503  }
1504 
1505  // Step 3: Iterate over all the secondary authentication providers.
1506 
1507  $beginReqs = $state['reqs'];
1508 
1509  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1510  if ( !isset( $state['secondary'][$id] ) ) {
1511  // This provider isn't started yet, so we pass it the set
1512  // of reqs from beginAuthentication instead of whatever
1513  // might have been used by a previous provider in line.
1514  $func = 'beginSecondaryAccountCreation';
1515  $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1516  } elseif ( !$state['secondary'][$id] ) {
1517  $func = 'continueSecondaryAccountCreation';
1518  $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1519  } else {
1520  continue;
1521  }
1522  switch ( $res->status ) {
1524  $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1525  'user' => $user->getName(),
1526  'creator' => $creator->getName(),
1527  ] );
1528  // fall through
1530  $state['secondary'][$id] = true;
1531  break;
1534  $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1535  'user' => $user->getName(),
1536  'creator' => $creator->getName(),
1537  ] );
1538  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1539  $state['secondary'][$id] = false;
1540  $state['continueRequests'] = $res->neededRequests;
1541  $session->setSecret( 'AuthManager::accountCreationState', $state );
1542  return $res;
1544  throw new \DomainException(
1545  get_class( $provider ) . "::{$func}() returned $res->status." .
1546  ' Secondary providers are not allowed to fail account creation, that' .
1547  ' should have been done via testForAccountCreation().'
1548  );
1549  // @codeCoverageIgnoreStart
1550  default:
1551  throw new \DomainException(
1552  get_class( $provider ) . "::{$func}() returned $res->status"
1553  );
1554  // @codeCoverageIgnoreEnd
1555  }
1556  }
1557 
1558  $id = $user->getId();
1559  $name = $user->getName();
1560  $req = new CreatedAccountAuthenticationRequest( $id, $name );
1561  $ret = AuthenticationResponse::newPass( $name );
1562  $ret->loginRequest = $req;
1563  $this->createdAccountAuthenticationRequests[] = $req;
1564 
1565  $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1566  'user' => $user->getName(),
1567  'creator' => $creator->getName(),
1568  ] );
1569 
1570  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1571  $session->remove( 'AuthManager::accountCreationState' );
1572  $this->removeAuthenticationSessionData( null );
1573  return $ret;
1574  } catch ( \Exception $ex ) {
1575  $session->remove( 'AuthManager::accountCreationState' );
1576  throw $ex;
1577  }
1578  }
1579 
1597  public function autoCreateUser( User $user, $source, $login = true ) {
1598  if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1599  $source !== self::AUTOCREATE_SOURCE_MAINT &&
1601  ) {
1602  throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1603  }
1604 
1605  $username = $user->getName();
1606 
1607  // Try the local user from the replica DB
1608  $localId = User::idFromName( $username );
1609  $flags = User::READ_NORMAL;
1610 
1611  // Fetch the user ID from the master, so that we don't try to create the user
1612  // when they already exist, due to replication lag
1613  // @codeCoverageIgnoreStart
1614  if (
1615  !$localId &&
1616  MediaWikiServices::getInstance()->getDBLoadBalancer()->getReaderIndex() !== 0
1617  ) {
1618  $localId = User::idFromName( $username, User::READ_LATEST );
1619  $flags = User::READ_LATEST;
1620  }
1621  // @codeCoverageIgnoreEnd
1622 
1623  if ( $localId ) {
1624  $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1625  'username' => $username,
1626  ] );
1627  $user->setId( $localId );
1628  $user->loadFromId( $flags );
1629  if ( $login ) {
1630  $this->setSessionDataForUser( $user );
1631  }
1632  $status = Status::newGood();
1633  $status->warning( 'userexists' );
1634  return $status;
1635  }
1636 
1637  // Wiki is read-only?
1638  if ( $this->readOnlyMode->isReadOnly() ) {
1639  $reason = $this->readOnlyMode->getReason();
1640  $this->logger->debug( __METHOD__ . ': denied because of read only mode: {reason}', [
1641  'username' => $username,
1642  'reason' => $reason,
1643  ] );
1644  $user->setId( 0 );
1645  $user->loadFromId();
1646  return Status::newFatal( wfMessage( 'readonlytext', $reason ) );
1647  }
1648 
1649  // Check the session, if we tried to create this user already there's
1650  // no point in retrying.
1651  $session = $this->request->getSession();
1652  if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1653  $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1654  'username' => $username,
1655  'sessionid' => $session->getId(),
1656  ] );
1657  $user->setId( 0 );
1658  $user->loadFromId();
1659  $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1660  if ( $reason instanceof StatusValue ) {
1661  return Status::wrap( $reason );
1662  } else {
1663  return Status::newFatal( $reason );
1664  }
1665  }
1666 
1667  // Is the username creatable?
1668  if ( !$this->userNameUtils->isCreatable( $username ) ) {
1669  $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1670  'username' => $username,
1671  ] );
1672  $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
1673  $user->setId( 0 );
1674  $user->loadFromId();
1675  return Status::newFatal( 'noname' );
1676  }
1677 
1678  // Is the IP user able to create accounts?
1679  $anon = new User;
1680  if ( $source !== self::AUTOCREATE_SOURCE_MAINT &&
1681  !$this->permManager->userHasAnyRight( $anon, 'createaccount', 'autocreateaccount' )
1682  ) {
1683  $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1684  'username' => $username,
1685  'clientip' => $anon->getName(),
1686  ] );
1687  $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
1688  $session->persist();
1689  $user->setId( 0 );
1690  $user->loadFromId();
1691  return Status::newFatal( 'authmanager-autocreate-noperm' );
1692  }
1693 
1694  // Avoid account creation races on double submissions
1696  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1697  if ( !$lock ) {
1698  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1699  'user' => $username,
1700  ] );
1701  $user->setId( 0 );
1702  $user->loadFromId();
1703  return Status::newFatal( 'usernameinprogress' );
1704  }
1705 
1706  // Denied by providers?
1707  $options = [
1708  'flags' => User::READ_LATEST,
1709  'creating' => true,
1710  ];
1711  $providers = $this->getPreAuthenticationProviders() +
1714  foreach ( $providers as $provider ) {
1715  $status = $provider->testUserForCreation( $user, $source, $options );
1716  if ( !$status->isGood() ) {
1717  $ret = Status::wrap( $status );
1718  $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1719  'username' => $username,
1720  'reason' => $ret->getWikiText( null, null, 'en' ),
1721  ] );
1722  $session->set( 'AuthManager::AutoCreateBlacklist', $status );
1723  $user->setId( 0 );
1724  $user->loadFromId();
1725  return $ret;
1726  }
1727  }
1728 
1729  $backoffKey = $cache->makeKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1730  if ( $cache->get( $backoffKey ) ) {
1731  $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1732  'username' => $username,
1733  ] );
1734  $user->setId( 0 );
1735  $user->loadFromId();
1736  return Status::newFatal( 'authmanager-autocreate-exception' );
1737  }
1738 
1739  // Checks passed, create the user...
1740  $from = $_SERVER['REQUEST_URI'] ?? 'CLI';
1741  $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1742  'username' => $username,
1743  'from' => $from,
1744  ] );
1745 
1746  // Ignore warnings about master connections/writes...hard to avoid here
1747  $trxProfiler = \Profiler::instance()->getTransactionProfiler();
1748  $old = $trxProfiler->setSilenced( true );
1749  try {
1750  $status = $user->addToDatabase();
1751  if ( !$status->isOK() ) {
1752  // Double-check for a race condition (T70012). We make use of the fact that when
1753  // addToDatabase fails due to the user already existing, the user object gets loaded.
1754  if ( $user->getId() ) {
1755  $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1756  'username' => $username,
1757  ] );
1758  if ( $login ) {
1759  $this->setSessionDataForUser( $user );
1760  }
1761  $status = Status::newGood();
1762  $status->warning( 'userexists' );
1763  } else {
1764  $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
1765  'username' => $username,
1766  'msg' => $status->getWikiText( null, null, 'en' )
1767  ] );
1768  $user->setId( 0 );
1769  $user->loadFromId();
1770  }
1771  return $status;
1772  }
1773  } catch ( \Exception $ex ) {
1774  $trxProfiler->setSilenced( $old );
1775  $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1776  'username' => $username,
1777  'exception' => $ex,
1778  ] );
1779  // Do not keep throwing errors for a while
1780  $cache->set( $backoffKey, 1, 600 );
1781  // Bubble up error; which should normally trigger DB rollbacks
1782  throw $ex;
1783  }
1784 
1785  $this->setDefaultUserOptions( $user, false );
1786 
1787  // Inform the providers
1788  $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1789 
1790  $this->getHookRunner()->onLocalUserCreated( $user, true );
1791  $user->saveSettings();
1792 
1793  // Update user count
1794  \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1795  // Watch user's userpage and talk page
1796  \DeferredUpdates::addCallableUpdate( function () use ( $user ) {
1797  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1798  } );
1799 
1800  // Log the creation
1801  if ( $this->config->get( 'NewUserLog' ) ) {
1802  $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1803  $logEntry->setPerformer( $user );
1804  $logEntry->setTarget( $user->getUserPage() );
1805  $logEntry->setComment( '' );
1806  $logEntry->setParameters( [
1807  '4::userid' => $user->getId(),
1808  ] );
1809  $logEntry->insert();
1810  }
1811 
1812  $trxProfiler->setSilenced( $old );
1813 
1814  if ( $login ) {
1815  $this->setSessionDataForUser( $user );
1816  }
1817 
1818  return Status::newGood();
1819  }
1820 
1832  public function canLinkAccounts() {
1833  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1834  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1835  return true;
1836  }
1837  }
1838  return false;
1839  }
1840 
1850  public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1851  $session = $this->request->getSession();
1852  $session->remove( 'AuthManager::accountLinkState' );
1853 
1854  if ( !$this->canLinkAccounts() ) {
1855  // Caller should have called canLinkAccounts()
1856  throw new \LogicException( 'Account linking is not possible' );
1857  }
1858 
1859  if ( $user->getId() === 0 ) {
1860  if ( !$this->userNameUtils->isUsable( $user->getName() ) ) {
1861  $msg = wfMessage( 'noname' );
1862  } else {
1863  $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1864  }
1865  return AuthenticationResponse::newFail( $msg );
1866  }
1867  foreach ( $reqs as $req ) {
1868  $req->username = $user->getName();
1869  $req->returnToUrl = $returnToUrl;
1870  }
1871 
1872  $this->removeAuthenticationSessionData( null );
1873 
1874  $providers = $this->getPreAuthenticationProviders();
1875  foreach ( $providers as $id => $provider ) {
1876  $status = $provider->testForAccountLink( $user );
1877  if ( !$status->isGood() ) {
1878  $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1879  'user' => $user->getName(),
1880  ] );
1882  Status::wrap( $status )->getMessage()
1883  );
1884  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1885  return $ret;
1886  }
1887  }
1888 
1889  $state = [
1890  'username' => $user->getName(),
1891  'userid' => $user->getId(),
1892  'returnToUrl' => $returnToUrl,
1893  'primary' => null,
1894  'continueRequests' => [],
1895  ];
1896 
1897  $providers = $this->getPrimaryAuthenticationProviders();
1898  foreach ( $providers as $id => $provider ) {
1899  if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1900  continue;
1901  }
1902 
1903  $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1904  switch ( $res->status ) {
1906  $this->logger->info( "Account linked to {user} by $id", [
1907  'user' => $user->getName(),
1908  ] );
1909  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1910  return $res;
1911 
1913  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1914  'user' => $user->getName(),
1915  ] );
1916  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1917  return $res;
1918 
1920  // Continue loop
1921  break;
1922 
1925  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1926  'user' => $user->getName(),
1927  ] );
1928  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1929  $state['primary'] = $id;
1930  $state['continueRequests'] = $res->neededRequests;
1931  $session->setSecret( 'AuthManager::accountLinkState', $state );
1932  $session->persist();
1933  return $res;
1934 
1935  // @codeCoverageIgnoreStart
1936  default:
1937  throw new \DomainException(
1938  get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1939  );
1940  // @codeCoverageIgnoreEnd
1941  }
1942  }
1943 
1944  $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1945  'user' => $user->getName(),
1946  ] );
1948  wfMessage( 'authmanager-link-no-primary' )
1949  );
1950  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1951  return $ret;
1952  }
1953 
1959  public function continueAccountLink( array $reqs ) {
1960  $session = $this->request->getSession();
1961  try {
1962  if ( !$this->canLinkAccounts() ) {
1963  // Caller should have called canLinkAccounts()
1964  $session->remove( 'AuthManager::accountLinkState' );
1965  throw new \LogicException( 'Account linking is not possible' );
1966  }
1967 
1968  $state = $session->getSecret( 'AuthManager::accountLinkState' );
1969  if ( !is_array( $state ) ) {
1971  wfMessage( 'authmanager-link-not-in-progress' )
1972  );
1973  }
1974  $state['continueRequests'] = [];
1975 
1976  // Step 0: Prepare and validate the input
1977 
1978  $user = User::newFromName( $state['username'], 'usable' );
1979  if ( !is_object( $user ) ) {
1980  $session->remove( 'AuthManager::accountLinkState' );
1981  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1982  }
1983  if ( $user->getId() !== $state['userid'] ) {
1984  throw new \UnexpectedValueException(
1985  "User \"{$state['username']}\" is valid, but " .
1986  "ID {$user->getId()} !== {$state['userid']}!"
1987  );
1988  }
1989 
1990  foreach ( $reqs as $req ) {
1991  $req->username = $state['username'];
1992  $req->returnToUrl = $state['returnToUrl'];
1993  }
1994 
1995  // Step 1: Call the primary again until it succeeds
1996 
1997  $provider = $this->getAuthenticationProvider( $state['primary'] );
1998  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1999  // Configuration changed? Force them to start over.
2000  // @codeCoverageIgnoreStart
2002  wfMessage( 'authmanager-link-not-in-progress' )
2003  );
2004  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
2005  $session->remove( 'AuthManager::accountLinkState' );
2006  return $ret;
2007  // @codeCoverageIgnoreEnd
2008  }
2009  $id = $provider->getUniqueId();
2010  $res = $provider->continuePrimaryAccountLink( $user, $reqs );
2011  switch ( $res->status ) {
2013  $this->logger->info( "Account linked to {user} by $id", [
2014  'user' => $user->getName(),
2015  ] );
2016  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
2017  $session->remove( 'AuthManager::accountLinkState' );
2018  return $res;
2020  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
2021  'user' => $user->getName(),
2022  ] );
2023  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
2024  $session->remove( 'AuthManager::accountLinkState' );
2025  return $res;
2028  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
2029  'user' => $user->getName(),
2030  ] );
2031  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
2032  $state['continueRequests'] = $res->neededRequests;
2033  $session->setSecret( 'AuthManager::accountLinkState', $state );
2034  return $res;
2035  default:
2036  throw new \DomainException(
2037  get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
2038  );
2039  }
2040  } catch ( \Exception $ex ) {
2041  $session->remove( 'AuthManager::accountLinkState' );
2042  throw $ex;
2043  }
2044  }
2045 
2071  public function getAuthenticationRequests( $action, User $user = null ) {
2072  $options = [];
2073  $providerAction = $action;
2074 
2075  // Figure out which providers to query
2076  switch ( $action ) {
2077  case self::ACTION_LOGIN:
2078  case self::ACTION_CREATE:
2079  $providers = $this->getPreAuthenticationProviders() +
2082  break;
2083 
2085  $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
2086  return is_array( $state ) ? $state['continueRequests'] : [];
2087 
2089  $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
2090  return is_array( $state ) ? $state['continueRequests'] : [];
2091 
2092  case self::ACTION_LINK:
2093  $providers = [];
2094  foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2095  if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2096  $providers[] = $p;
2097  }
2098  }
2099  break;
2100 
2101  case self::ACTION_UNLINK:
2102  $providers = [];
2103  foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2104  if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2105  $providers[] = $p;
2106  }
2107  }
2108 
2109  // To providers, unlink and remove are identical.
2110  $providerAction = self::ACTION_REMOVE;
2111  break;
2112 
2114  $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
2115  return is_array( $state ) ? $state['continueRequests'] : [];
2116 
2117  case self::ACTION_CHANGE:
2118  case self::ACTION_REMOVE:
2119  $providers = $this->getPrimaryAuthenticationProviders() +
2121  break;
2122 
2123  // @codeCoverageIgnoreStart
2124  default:
2125  throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2126  }
2127  // @codeCoverageIgnoreEnd
2128 
2129  return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2130  }
2131 
2142  $providerAction, array $options, array $providers, User $user = null
2143  ) {
2144  $user = $user ?: \RequestContext::getMain()->getUser();
2145  $options['username'] = $user->isAnon() ? null : $user->getName();
2146 
2147  // Query them and merge results
2148  $reqs = [];
2149  foreach ( $providers as $provider ) {
2150  $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2151  foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2152  $id = $req->getUniqueId();
2153 
2154  // If a required request if from a Primary, mark it as "primary-required" instead
2155  if ( $isPrimary && $req->required ) {
2156  $req->required = AuthenticationRequest::PRIMARY_REQUIRED;
2157  }
2158 
2159  if (
2160  !isset( $reqs[$id] )
2161  || $req->required === AuthenticationRequest::REQUIRED
2162  || $reqs[$id] === AuthenticationRequest::OPTIONAL
2163  ) {
2164  $reqs[$id] = $req;
2165  }
2166  }
2167  }
2168 
2169  // AuthManager has its own req for some actions
2170  switch ( $providerAction ) {
2171  case self::ACTION_LOGIN:
2172  $reqs[] = new RememberMeAuthenticationRequest;
2173  break;
2174 
2175  case self::ACTION_CREATE:
2176  $reqs[] = new UsernameAuthenticationRequest;
2177  $reqs[] = new UserDataAuthenticationRequest;
2178  if ( $options['username'] !== null ) {
2180  $options['username'] = null; // Don't fill in the username below
2181  }
2182  break;
2183  }
2184 
2185  // Fill in reqs data
2186  $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2187 
2188  // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2189  if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2190  $reqs = array_filter( $reqs, function ( $req ) {
2191  return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2192  } );
2193  }
2194 
2195  return array_values( $reqs );
2196  }
2197 
2205  private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2206  foreach ( $reqs as $req ) {
2207  if ( !$req->action || $forceAction ) {
2208  $req->action = $action;
2209  }
2210  if ( $req->username === null ) {
2211  $req->username = $username;
2212  }
2213  }
2214  }
2215 
2222  public function userExists( $username, $flags = User::READ_NORMAL ) {
2223  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2224  if ( $provider->testUserExists( $username, $flags ) ) {
2225  return true;
2226  }
2227  }
2228 
2229  return false;
2230  }
2231 
2243  public function allowsPropertyChange( $property ) {
2244  $providers = $this->getPrimaryAuthenticationProviders() +
2246  foreach ( $providers as $provider ) {
2247  if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2248  return false;
2249  }
2250  }
2251  return true;
2252  }
2253 
2262  public function getAuthenticationProvider( $id ) {
2263  // Fast version
2264  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2265  return $this->allAuthenticationProviders[$id];
2266  }
2267 
2268  // Slow version: instantiate each kind and check
2269  $providers = $this->getPrimaryAuthenticationProviders();
2270  if ( isset( $providers[$id] ) ) {
2271  return $providers[$id];
2272  }
2273  $providers = $this->getSecondaryAuthenticationProviders();
2274  if ( isset( $providers[$id] ) ) {
2275  return $providers[$id];
2276  }
2277  $providers = $this->getPreAuthenticationProviders();
2278  if ( isset( $providers[$id] ) ) {
2279  return $providers[$id];
2280  }
2281 
2282  return null;
2283  }
2284 
2298  public function setAuthenticationSessionData( $key, $data ) {
2299  $session = $this->request->getSession();
2300  $arr = $session->getSecret( 'authData' );
2301  if ( !is_array( $arr ) ) {
2302  $arr = [];
2303  }
2304  $arr[$key] = $data;
2305  $session->setSecret( 'authData', $arr );
2306  }
2307 
2315  public function getAuthenticationSessionData( $key, $default = null ) {
2316  $arr = $this->request->getSession()->getSecret( 'authData' );
2317  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2318  return $arr[$key];
2319  } else {
2320  return $default;
2321  }
2322  }
2323 
2329  public function removeAuthenticationSessionData( $key ) {
2330  $session = $this->request->getSession();
2331  if ( $key === null ) {
2332  $session->remove( 'authData' );
2333  } else {
2334  $arr = $session->getSecret( 'authData' );
2335  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2336  unset( $arr[$key] );
2337  $session->setSecret( 'authData', $arr );
2338  }
2339  }
2340  }
2341 
2348  protected function providerArrayFromSpecs( $class, array $specs ) {
2349  $i = 0;
2350  foreach ( $specs as &$spec ) {
2351  $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2352  }
2353  unset( $spec );
2354  // Sort according to the 'sort' field, and if they are equal, according to 'sort2'
2355  usort( $specs, function ( $a, $b ) {
2356  return $a['sort'] <=> $b['sort']
2357  ?: $a['sort2'] <=> $b['sort2'];
2358  } );
2359 
2360  $ret = [];
2361  foreach ( $specs as $spec ) {
2363  $provider = $this->objectFactory->createObject( $spec, [ 'assertClass' => $class ] );
2364  $provider->setLogger( $this->logger );
2365  $provider->setManager( $this );
2366  $provider->setConfig( $this->config );
2367  $provider->setHookContainer( $this->getHookContainer() );
2368  $id = $provider->getUniqueId();
2369  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2370  throw new \RuntimeException(
2371  "Duplicate specifications for id $id (classes " .
2372  get_class( $provider ) . ' and ' .
2373  get_class( $this->allAuthenticationProviders[$id] ) . ')'
2374  );
2375  }
2376  $this->allAuthenticationProviders[$id] = $provider;
2377  $ret[$id] = $provider;
2378  }
2379  return $ret;
2380  }
2381 
2385  private function getConfiguration() {
2386  return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2387  }
2388 
2393  protected function getPreAuthenticationProviders() {
2394  if ( $this->preAuthenticationProviders === null ) {
2395  $conf = $this->getConfiguration();
2396  $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2397  PreAuthenticationProvider::class, $conf['preauth']
2398  );
2399  }
2401  }
2402 
2407  protected function getPrimaryAuthenticationProviders() {
2408  if ( $this->primaryAuthenticationProviders === null ) {
2409  $conf = $this->getConfiguration();
2410  $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2411  PrimaryAuthenticationProvider::class, $conf['primaryauth']
2412  );
2413  }
2415  }
2416 
2422  if ( $this->secondaryAuthenticationProviders === null ) {
2423  $conf = $this->getConfiguration();
2424  $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2425  SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2426  );
2427  }
2429  }
2430 
2436  private function setSessionDataForUser( $user, $remember = null ) {
2437  $session = $this->request->getSession();
2438  $delay = $session->delaySave();
2439 
2440  $session->resetId();
2441  $session->resetAllTokens();
2442  if ( $session->canSetUser() ) {
2443  $session->setUser( $user );
2444  }
2445  if ( $remember !== null ) {
2446  $session->setRememberUser( $remember );
2447  }
2448  $session->set( 'AuthManager:lastAuthId', $user->getId() );
2449  $session->set( 'AuthManager:lastAuthTimestamp', time() );
2450  $session->persist();
2451 
2452  \Wikimedia\ScopedCallback::consume( $delay );
2453 
2454  $this->getHookRunner()->onUserLoggedIn( $user );
2455  }
2456 
2461  private function setDefaultUserOptions( User $user, $useContextLang ) {
2462  $user->setToken();
2463 
2464  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2465 
2466  $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $contLang;
2467  $user->setOption( 'language', $lang->getPreferredVariant() );
2468 
2469  $contLangConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
2470  ->getLanguageConverter();
2471  if ( $contLangConverter->hasVariants() ) {
2472  $user->setOption( 'variant', $contLangConverter->getPreferredVariant() );
2473  }
2474  }
2475 
2481  private function callMethodOnProviders( $which, $method, array $args ) {
2482  $providers = [];
2483  if ( $which & 1 ) {
2484  $providers += $this->getPreAuthenticationProviders();
2485  }
2486  if ( $which & 2 ) {
2487  $providers += $this->getPrimaryAuthenticationProviders();
2488  }
2489  if ( $which & 4 ) {
2490  $providers += $this->getSecondaryAuthenticationProviders();
2491  }
2492  foreach ( $providers as $provider ) {
2493  $provider->$method( ...$args );
2494  }
2495  }
2496 
2502  public static function resetCache() {
2503  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2504  // @codeCoverageIgnoreStart
2505  throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2506  // @codeCoverageIgnoreEnd
2507  }
2508 
2509  self::$instance = null;
2510  }
2511 
2515  private function getHookContainer() {
2516  return $this->hookContainer;
2517  }
2518 
2522  private function getHookRunner() {
2523  return $this->hookRunner;
2524  }
2525 
2528 }
2529 
MediaWiki\Auth\AuthManager\continueAccountLink
continueAccountLink(array $reqs)
Continue an account linking flow.
Definition: AuthManager.php:1959
User\loadFromId
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:410
MediaWiki\Auth\AuthManager\continueAuthentication
continueAuthentication(array $reqs)
Continue an authentication flow.
Definition: AuthManager.php:450
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:236
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:156
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:584
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:121
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:2024
MediaWiki\Auth\AuthManager\fillRequests
fillRequests(array &$reqs, $action, $username, $forceAction=false)
Set values in an array of requests.
Definition: AuthManager.php:2205
MediaWiki\Auth\AuthManager\changeAuthenticationData
changeAuthenticationData(AuthenticationRequest $req, $isAddition=false)
Change authentication data (e.g.
Definition: AuthManager.php:946
MediaWiki\Auth\AuthManager\getPrimaryAuthenticationProviders
getPrimaryAuthenticationProviders()
Get the list of PrimaryAuthenticationProviders.
Definition: AuthManager.php:2407
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:165
MediaWiki\Auth\AuthManager\ACTION_UNLINK
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
Definition: AuthManager.php:116
$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:126
MediaWiki\Auth\AuthManager\getHookContainer
getHookContainer()
Definition: AuthManager.php:2515
MediaWiki\Auth\AuthManager\revokeAccessForUser
revokeAccessForUser( $username)
Revoke any authentication credentials for a user.
Definition: AuthManager.php:888
MediaWiki\Auth\AuthManager\$userNameUtils
UserNameUtils $userNameUtils
Definition: AuthManager.php:150
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\autoCreateUser
autoCreateUser(User $user, $source, $login=true)
Auto-create an account, and log into that account.
Definition: AuthManager.php:1597
MediaWiki\Auth\AuthManager\$instance
static AuthManager null $instance
Definition: AuthManager.php:132
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:325
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:545
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1220
User\getUserPage
getUserPage()
Get this user's personal page title.
Definition: User.php:3710
MediaWiki\Auth\AuthManager\userExists
userExists( $username, $flags=User::READ_NORMAL)
Determine whether a username exists.
Definition: AuthManager.php:2222
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:106
MediaWiki\Auth\AuthManager\$allAuthenticationProviders
AuthenticationProvider[] $allAuthenticationProviders
Definition: AuthManager.php:153
MediaWiki\Auth\AuthManager\AUTOCREATE_SOURCE_MAINT
const AUTOCREATE_SOURCE_MAINT
Auto-creation is due to a Maintenance script.
Definition: AuthManager.php:129
MediaWiki\Auth\AuthManager\getPreAuthenticationProviders
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
Definition: AuthManager.php:2393
MediaWiki\Auth\AuthManager\getAuthenticationSessionData
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
Definition: AuthManager.php:2315
$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:98
MediaWiki\Auth\AuthManager\$blockManager
BlockManager $blockManager
Definition: AuthManager.php:177
MediaWiki\Auth\AuthManager\SEC_FAIL
const SEC_FAIL
Security-sensitive should not be performed.
Definition: AuthManager.php:123
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:168
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:2329
MediaWiki\MediaWikiServices\getInstance
static getInstance()
Returns the global default instance of the top level service locator.
Definition: MediaWikiServices.php:197
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:3541
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:1094
MediaWiki\Auth\AuthManager\getAuthenticationProvider
getAuthenticationProvider( $id)
Get a provider by ID.
Definition: AuthManager.php:2262
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:110
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:972
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:174
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:2385
$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\__construct
__construct(WebRequest $request, Config $config, ObjectFactory $objectFactory, PermissionManager $permManager, HookContainer $hookContainer, ReadOnlyMode $readOnlyMode, UserNameUtils $userNameUtils, BlockManager $blockManager, BlockErrorFormatter $blockErrorFormatter)
Definition: AuthManager.php:202
MediaWiki\Auth\AuthManager\canAuthenticateNow
canAuthenticateNow()
Indicate whether user authentication is possible.
Definition: AuthManager.php:303
MediaWiki\Auth\AuthManager\setAuthenticationSessionData
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
Definition: AuthManager.php:2298
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:71
MediaWiki\Auth\AuthManager\$permManager
PermissionManager $permManager
Definition: AuthManager.php:147
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:2081
MediaWiki\Auth\AuthManager\setDefaultUserOptions
setDefaultUserOptions(User $user, $useContextLang)
Definition: AuthManager.php:2461
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:2502
User\saveSettings
saveSettings()
Save this user's settings into the database.
Definition: User.php:3358
MediaWiki\Auth\AuthManager\$request
WebRequest $request
Definition: AuthManager.php:135
MediaWiki\Auth\AuthManager\beginAccountLink
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
Definition: AuthManager.php:1850
MediaWiki\Auth\AuthManager\normalizeUsername
normalizeUsername( $username)
Provide normalized versions of the username for security checks.
Definition: AuthManager.php:863
MediaWiki\Auth\AuthManager\canLinkAccounts
canLinkAccounts()
Determine whether accounts can be linked.
Definition: AuthManager.php:1832
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:104
MediaWiki\Auth\AuthManager\ACTION_CREATE
const ACTION_CREATE
Create a new user.
Definition: AuthManager.php:100
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:50
MediaWiki\Auth\AuthManager\$logger
LoggerInterface $logger
Definition: AuthManager.php:144
MediaWiki\Auth\AuthManager\$primaryAuthenticationProviders
PrimaryAuthenticationProvider[] $primaryAuthenticationProviders
Definition: AuthManager.php:159
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:1195
BotPassword\invalidateAllPasswordsForUser
static invalidateAllPasswordsForUser( $username)
Invalidate all passwords for a user, by name.
Definition: BotPassword.php:347
MediaWiki\Auth\AuthManager\$secondaryAuthenticationProviders
SecondaryAuthenticationProvider[] $secondaryAuthenticationProviders
Definition: AuthManager.php:162
MediaWiki\Auth\AuthManager\forcePrimaryAuthenticationProviders
forcePrimaryAuthenticationProviders(array $providers, $why)
Force certain PrimaryAuthenticationProviders.
Definition: AuthManager.php:246
MediaWiki\Auth\AuthManager\getHookRunner
getHookRunner()
Definition: AuthManager.php:2522
MediaWiki\Auth\AuthManager\ACTION_CHANGE
const ACTION_CHANGE
Change a user's credentials.
Definition: AuthManager.php:112
User\addWatch
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition: User.php:3157
MediaWiki\Auth\AuthManager\callMethodOnProviders
callMethodOnProviders( $which, $method, array $args)
Definition: AuthManager.php:2481
MediaWiki\Auth\AuthManager\ACTION_LINK
const ACTION_LINK
Link an existing user to a third-party account.
Definition: AuthManager.php:106
MediaWiki\Auth\AuthManager\allowsPropertyChange
allowsPropertyChange( $property)
Determine whether a user property should be allowed to be changed.
Definition: AuthManager.php:2243
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:454
MediaWiki\Auth\AuthManager\securitySensitiveOperationStatus
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
Definition: AuthManager.php:763
MediaWiki\Auth\AuthManager
This serves as the entry point to the authentication system.
Definition: AuthManager.php:92
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:171
MediaWiki\Auth\AuthManager\ACTION_REMOVE
const ACTION_REMOVE
Remove a user's credentials.
Definition: AuthManager.php:114
MediaWiki\Auth\AuthManager\SEC_OK
const SEC_OK
Security-sensitive operations are ok.
Definition: AuthManager.php:119
User\setId
setId( $v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2044
MediaWiki\$action
string $action
Cache what action this request is.
Definition: MediaWiki.php:45
MediaWiki\Auth\AuthManager\$objectFactory
ObjectFactory $objectFactory
Definition: AuthManager.php:141
MediaWiki\Auth\AuthManager\getAuthenticationRequests
getAuthenticationRequests( $action, User $user=null)
Return the applicable list of AuthenticationRequests.
Definition: AuthManager.php:2071
$cache
$cache
Definition: mcc.php:33
MediaWiki\Auth\AuthManager\singleton
static singleton()
Get the global AuthManager.
Definition: AuthManager.php:187
User\setToken
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2432
User\idFromName
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:892
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:2436
MediaWiki\Auth\AuthManager\$createdAccountAuthenticationRequests
CreatedAccountAuthenticationRequest[] $createdAccountAuthenticationRequests
Definition: AuthManager.php:165
MediaWiki\Auth\AuthManager\$config
Config $config
Definition: AuthManager.php:138
MediaWiki\Auth\AuthManager\canCreateAccount
canCreateAccount( $username, $options=[])
Determine whether a particular account can be created.
Definition: AuthManager.php:991
$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:94
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
MediaWiki\Auth\AuthManager\getSecondaryAuthenticationProviders
getSecondaryAuthenticationProviders()
Get the list of SecondaryAuthenticationProviders.
Definition: AuthManager.php:2421
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:567
MediaWiki\Auth\AuthManager\userCanAuthenticate
userCanAuthenticate( $username)
Determine whether a username can authenticate.
Definition: AuthManager.php:840
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
MediaWiki\Auth\AuthManager\allowsAuthenticationDataChange
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
Definition: AuthManager.php:904
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:3657
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:2633
MediaWiki\Auth
Definition: AbstractAuthenticationProvider.php:22
MediaWiki\Auth\AuthManager\setLogger
setLogger(LoggerInterface $logger)
Definition: AuthManager.php:229
MediaWiki\Auth\AuthManager\providerArrayFromSpecs
providerArrayFromSpecs( $class, array $specs)
Create an array of AuthenticationProviders from an array of ObjectFactory specs.
Definition: AuthManager.php:2348
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:2053
MediaWiki\Auth\AuthManager\checkAccountCreatePermissions
checkAccountCreatePermissions(User $creator)
Basic permissions checks on whether a user can create accounts.
Definition: AuthManager.php:1039
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:180
MediaWiki\Auth\AuthManager\getAuthenticationRequestsInternal
getAuthenticationRequestsInternal( $providerAction, array $options, array $providers, User $user=null)
Internal request lookup for self::getAuthenticationRequests.
Definition: AuthManager.php:2141
MediaWiki\Auth\AuthenticationProvider
An AuthenticationProvider is used by AuthManager when authenticating users.
Definition: AuthenticationProvider.php:40