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