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