MediaWiki  1.27.2
AuthManager.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
26 use Config;
29 use Status;
31 use User;
33 
43 class AuthManager implements LoggerAwareInterface {
45  const ACTION_LOGIN = 'login';
48  const ACTION_LOGIN_CONTINUE = 'login-continue';
50  const ACTION_CREATE = 'create';
53  const ACTION_CREATE_CONTINUE = 'create-continue';
55  const ACTION_LINK = 'link';
58  const ACTION_LINK_CONTINUE = 'link-continue';
60  const ACTION_CHANGE = 'change';
62  const ACTION_REMOVE = 'remove';
64  const ACTION_UNLINK = 'unlink';
65 
67  const SEC_OK = 'ok';
69  const SEC_REAUTH = 'reauth';
71  const SEC_FAIL = 'fail';
72 
75 
77  private static $instance = null;
78 
80  private $request;
81 
83  private $config;
84 
86  private $logger;
87 
90 
93 
96 
99 
102 
107  public static function singleton() {
108  if ( self::$instance === null ) {
109  self::$instance = new self(
110  \RequestContext::getMain()->getRequest(),
111  \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
112  );
113  }
114  return self::$instance;
115  }
116 
122  $this->request = $request;
123  $this->config = $config;
124  $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
125  }
126 
130  public function setLogger( LoggerInterface $logger ) {
131  $this->logger = $logger;
132  }
133 
137  public function getRequest() {
138  return $this->request;
139  }
140 
147  public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
148  $this->logger->warning( "Overriding AuthManager primary authn because $why" );
149 
150  if ( $this->primaryAuthenticationProviders !== null ) {
151  $this->logger->warning(
152  'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
153  );
154 
155  $this->allAuthenticationProviders = array_diff_key(
156  $this->allAuthenticationProviders,
157  $this->primaryAuthenticationProviders
158  );
159  $session = $this->request->getSession();
160  $session->remove( 'AuthManager::authnState' );
161  $session->remove( 'AuthManager::accountCreationState' );
162  $session->remove( 'AuthManager::accountLinkState' );
163  $this->createdAccountAuthenticationRequests = [];
164  }
165 
166  $this->primaryAuthenticationProviders = [];
167  foreach ( $providers as $provider ) {
168  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
169  throw new \RuntimeException(
170  'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
171  get_class( $provider )
172  );
173  }
174  $provider->setLogger( $this->logger );
175  $provider->setManager( $this );
176  $provider->setConfig( $this->config );
177  $id = $provider->getUniqueId();
178  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
179  throw new \RuntimeException(
180  "Duplicate specifications for id $id (classes " .
181  get_class( $provider ) . ' and ' .
182  get_class( $this->allAuthenticationProviders[$id] ) . ')'
183  );
184  }
185  $this->allAuthenticationProviders[$id] = $provider;
186  $this->primaryAuthenticationProviders[$id] = $provider;
187  }
188  }
189 
199  public static function callLegacyAuthPlugin( $method, array $params, $return = null ) {
200  global $wgAuth;
201 
202  if ( $wgAuth && !$wgAuth instanceof AuthManagerAuthPlugin ) {
203  return call_user_func_array( [ $wgAuth, $method ], $params );
204  } else {
205  return $return;
206  }
207  }
208 
222  public function canAuthenticateNow() {
223  return $this->request->getSession()->canSetUser();
224  }
225 
244  public function beginAuthentication( array $reqs, $returnToUrl ) {
245  $session = $this->request->getSession();
246  if ( !$session->canSetUser() ) {
247  // Caller should have called canAuthenticateNow()
248  $session->remove( 'AuthManager::authnState' );
249  throw new \LogicException( 'Authentication is not possible now' );
250  }
251 
252  $guessUserName = null;
253  foreach ( $reqs as $req ) {
254  $req->returnToUrl = $returnToUrl;
255  // @codeCoverageIgnoreStart
256  if ( $req->username !== null && $req->username !== '' ) {
257  if ( $guessUserName === null ) {
258  $guessUserName = $req->username;
259  } elseif ( $guessUserName !== $req->username ) {
260  $guessUserName = null;
261  break;
262  }
263  }
264  // @codeCoverageIgnoreEnd
265  }
266 
267  // Check for special-case login of a just-created account
270  );
271  if ( $req ) {
272  if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
273  throw new \LogicException(
274  'CreatedAccountAuthenticationRequests are only valid on ' .
275  'the same AuthManager that created the account'
276  );
277  }
278 
279  $user = User::newFromName( $req->username );
280  // @codeCoverageIgnoreStart
281  if ( !$user ) {
282  throw new \UnexpectedValueException(
283  "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
284  );
285  } elseif ( $user->getId() != $req->id ) {
286  throw new \UnexpectedValueException(
287  "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
288  );
289  }
290  // @codeCoverageIgnoreEnd
291 
292  $this->logger->info( 'Logging in {user} after account creation', [
293  'user' => $user->getName(),
294  ] );
296  $this->setSessionDataForUser( $user );
297  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
298  $session->remove( 'AuthManager::authnState' );
299  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
300  return $ret;
301  }
302 
303  $this->removeAuthenticationSessionData( null );
304 
305  foreach ( $this->getPreAuthenticationProviders() as $provider ) {
306  $status = $provider->testForAuthentication( $reqs );
307  if ( !$status->isGood() ) {
308  $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
310  Status::wrap( $status )->getMessage()
311  );
312  $this->callMethodOnProviders( 7, 'postAuthentication',
313  [ User::newFromName( $guessUserName ) ?: null, $ret ]
314  );
315  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, null, $guessUserName ] );
316  return $ret;
317  }
318  }
319 
320  $state = [
321  'reqs' => $reqs,
322  'returnToUrl' => $returnToUrl,
323  'guessUserName' => $guessUserName,
324  'primary' => null,
325  'primaryResponse' => null,
326  'secondary' => [],
327  'maybeLink' => [],
328  'continueRequests' => [],
329  ];
330 
331  // Preserve state from a previous failed login
334  );
335  if ( $req ) {
336  $state['maybeLink'] = $req->maybeLink;
337  }
338 
339  $session = $this->request->getSession();
340  $session->setSecret( 'AuthManager::authnState', $state );
341  $session->persist();
342 
343  return $this->continueAuthentication( $reqs );
344  }
345 
368  public function continueAuthentication( array $reqs ) {
369  $session = $this->request->getSession();
370  try {
371  if ( !$session->canSetUser() ) {
372  // Caller should have called canAuthenticateNow()
373  // @codeCoverageIgnoreStart
374  throw new \LogicException( 'Authentication is not possible now' );
375  // @codeCoverageIgnoreEnd
376  }
377 
378  $state = $session->getSecret( 'AuthManager::authnState' );
379  if ( !is_array( $state ) ) {
381  wfMessage( 'authmanager-authn-not-in-progress' )
382  );
383  }
384  $state['continueRequests'] = [];
385 
386  $guessUserName = $state['guessUserName'];
387 
388  foreach ( $reqs as $req ) {
389  $req->returnToUrl = $state['returnToUrl'];
390  }
391 
392  // Step 1: Choose an primary authentication provider, and call it until it succeeds.
393 
394  if ( $state['primary'] === null ) {
395  // We haven't picked a PrimaryAuthenticationProvider yet
396  // @codeCoverageIgnoreStart
397  $guessUserName = null;
398  foreach ( $reqs as $req ) {
399  if ( $req->username !== null && $req->username !== '' ) {
400  if ( $guessUserName === null ) {
401  $guessUserName = $req->username;
402  } elseif ( $guessUserName !== $req->username ) {
403  $guessUserName = null;
404  break;
405  }
406  }
407  }
408  $state['guessUserName'] = $guessUserName;
409  // @codeCoverageIgnoreEnd
410  $state['reqs'] = $reqs;
411 
412  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
413  $res = $provider->beginPrimaryAuthentication( $reqs );
414  switch ( $res->status ) {
416  $state['primary'] = $id;
417  $state['primaryResponse'] = $res;
418  $this->logger->debug( "Primary login with $id succeeded" );
419  break 2;
421  $this->logger->debug( "Login failed in primary authentication by $id" );
422  if ( $res->createRequest || $state['maybeLink'] ) {
423  $res->createRequest = new CreateFromLoginAuthenticationRequest(
424  $res->createRequest, $state['maybeLink']
425  );
426  }
427  $this->callMethodOnProviders( 7, 'postAuthentication',
428  [ User::newFromName( $guessUserName ) ?: null, $res ]
429  );
430  $session->remove( 'AuthManager::authnState' );
431  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
432  return $res;
434  // Continue loop
435  break;
438  $this->logger->debug( "Primary login with $id returned $res->status" );
439  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
440  $state['primary'] = $id;
441  $state['continueRequests'] = $res->neededRequests;
442  $session->setSecret( 'AuthManager::authnState', $state );
443  return $res;
444 
445  // @codeCoverageIgnoreStart
446  default:
447  throw new \DomainException(
448  get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
449  );
450  // @codeCoverageIgnoreEnd
451  }
452  }
453  if ( $state['primary'] === null ) {
454  $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
456  wfMessage( 'authmanager-authn-no-primary' )
457  );
458  $this->callMethodOnProviders( 7, 'postAuthentication',
459  [ User::newFromName( $guessUserName ) ?: null, $ret ]
460  );
461  $session->remove( 'AuthManager::authnState' );
462  return $ret;
463  }
464  } elseif ( $state['primaryResponse'] === null ) {
465  $provider = $this->getAuthenticationProvider( $state['primary'] );
466  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
467  // Configuration changed? Force them to start over.
468  // @codeCoverageIgnoreStart
470  wfMessage( 'authmanager-authn-not-in-progress' )
471  );
472  $this->callMethodOnProviders( 7, 'postAuthentication',
473  [ User::newFromName( $guessUserName ) ?: null, $ret ]
474  );
475  $session->remove( 'AuthManager::authnState' );
476  return $ret;
477  // @codeCoverageIgnoreEnd
478  }
479  $id = $provider->getUniqueId();
480  $res = $provider->continuePrimaryAuthentication( $reqs );
481  switch ( $res->status ) {
483  $state['primaryResponse'] = $res;
484  $this->logger->debug( "Primary login with $id succeeded" );
485  break;
487  $this->logger->debug( "Login failed in primary authentication by $id" );
488  if ( $res->createRequest || $state['maybeLink'] ) {
489  $res->createRequest = new CreateFromLoginAuthenticationRequest(
490  $res->createRequest, $state['maybeLink']
491  );
492  }
493  $this->callMethodOnProviders( 7, 'postAuthentication',
494  [ User::newFromName( $guessUserName ) ?: null, $res ]
495  );
496  $session->remove( 'AuthManager::authnState' );
497  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
498  return $res;
501  $this->logger->debug( "Primary login with $id returned $res->status" );
502  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
503  $state['continueRequests'] = $res->neededRequests;
504  $session->setSecret( 'AuthManager::authnState', $state );
505  return $res;
506  default:
507  throw new \DomainException(
508  get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
509  );
510  }
511  }
512 
513  $res = $state['primaryResponse'];
514  if ( $res->username === null ) {
515  $provider = $this->getAuthenticationProvider( $state['primary'] );
516  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
517  // Configuration changed? Force them to start over.
518  // @codeCoverageIgnoreStart
520  wfMessage( 'authmanager-authn-not-in-progress' )
521  );
522  $this->callMethodOnProviders( 7, 'postAuthentication',
523  [ User::newFromName( $guessUserName ) ?: null, $ret ]
524  );
525  $session->remove( 'AuthManager::authnState' );
526  return $ret;
527  // @codeCoverageIgnoreEnd
528  }
529 
530  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
531  $res->linkRequest &&
532  // don't confuse the user with an incorrect message if linking is disabled
534  ) {
535  $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
536  $msg = 'authmanager-authn-no-local-user-link';
537  } else {
538  $msg = 'authmanager-authn-no-local-user';
539  }
540  $this->logger->debug(
541  "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
542  );
544  $ret->neededRequests = $this->getAuthenticationRequestsInternal(
545  self::ACTION_LOGIN,
546  [],
548  );
549  if ( $res->createRequest || $state['maybeLink'] ) {
550  $ret->createRequest = new CreateFromLoginAuthenticationRequest(
551  $res->createRequest, $state['maybeLink']
552  );
553  $ret->neededRequests[] = $ret->createRequest;
554  }
555  $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
556  $session->setSecret( 'AuthManager::authnState', [
557  'reqs' => [], // Will be filled in later
558  'primary' => null,
559  'primaryResponse' => null,
560  'secondary' => [],
561  'continueRequests' => $ret->neededRequests,
562  ] + $state );
563  return $ret;
564  }
565 
566  // Step 2: Primary authentication succeeded, create the User object
567  // (and add the user locally if necessary)
568 
569  $user = User::newFromName( $res->username, 'usable' );
570  if ( !$user ) {
571  throw new \DomainException(
572  get_class( $provider ) . " returned an invalid username: {$res->username}"
573  );
574  }
575  if ( $user->getId() === 0 ) {
576  // User doesn't exist locally. Create it.
577  $this->logger->info( 'Auto-creating {user} on login', [
578  'user' => $user->getName(),
579  ] );
580  $status = $this->autoCreateUser( $user, $state['primary'], false );
581  if ( !$status->isGood() ) {
583  Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
584  );
585  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
586  $session->remove( 'AuthManager::authnState' );
587  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
588  return $ret;
589  }
590  }
591 
592  // Step 3: Iterate over all the secondary authentication providers.
593 
594  $beginReqs = $state['reqs'];
595 
596  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
597  if ( !isset( $state['secondary'][$id] ) ) {
598  // This provider isn't started yet, so we pass it the set
599  // of reqs from beginAuthentication instead of whatever
600  // might have been used by a previous provider in line.
601  $func = 'beginSecondaryAuthentication';
602  $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
603  } elseif ( !$state['secondary'][$id] ) {
604  $func = 'continueSecondaryAuthentication';
605  $res = $provider->continueSecondaryAuthentication( $user, $reqs );
606  } else {
607  continue;
608  }
609  switch ( $res->status ) {
611  $this->logger->debug( "Secondary login with $id succeeded" );
612  // fall through
614  $state['secondary'][$id] = true;
615  break;
617  $this->logger->debug( "Login failed in secondary authentication by $id" );
618  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
619  $session->remove( 'AuthManager::authnState' );
620  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, $user, $user->getName() ] );
621  return $res;
624  $this->logger->debug( "Secondary login with $id returned " . $res->status );
625  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
626  $state['secondary'][$id] = false;
627  $state['continueRequests'] = $res->neededRequests;
628  $session->setSecret( 'AuthManager::authnState', $state );
629  return $res;
630 
631  // @codeCoverageIgnoreStart
632  default:
633  throw new \DomainException(
634  get_class( $provider ) . "::{$func}() returned $res->status"
635  );
636  // @codeCoverageIgnoreEnd
637  }
638  }
639 
640  // Step 4: Authentication complete! Set the user in the session and
641  // clean up.
642 
643  $this->logger->info( 'Login for {user} succeeded', [
644  'user' => $user->getName(),
645  ] );
648  );
649  $this->setSessionDataForUser( $user, $req && $req->rememberMe );
651  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
652  $session->remove( 'AuthManager::authnState' );
653  $this->removeAuthenticationSessionData( null );
654  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
655  return $ret;
656  } catch ( \Exception $ex ) {
657  $session->remove( 'AuthManager::authnState' );
658  throw $ex;
659  }
660  }
661 
673  public function securitySensitiveOperationStatus( $operation ) {
674  $status = self::SEC_OK;
675 
676  $this->logger->debug( __METHOD__ . ": Checking $operation" );
677 
678  $session = $this->request->getSession();
679  $aId = $session->getUser()->getId();
680  if ( $aId === 0 ) {
681  // User isn't authenticated. DWIM?
682  $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
683  $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
684  return $status;
685  }
686 
687  if ( $session->canSetUser() ) {
688  $id = $session->get( 'AuthManager:lastAuthId' );
689  $last = $session->get( 'AuthManager:lastAuthTimestamp' );
690  if ( $id !== $aId || $last === null ) {
691  $timeSinceLogin = PHP_INT_MAX; // Forever ago
692  } else {
693  $timeSinceLogin = max( 0, time() - $last );
694  }
695 
696  $thresholds = $this->config->get( 'ReauthenticateTime' );
697  if ( isset( $thresholds[$operation] ) ) {
698  $threshold = $thresholds[$operation];
699  } elseif ( isset( $thresholds['default'] ) ) {
700  $threshold = $thresholds['default'];
701  } else {
702  throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
703  }
704 
705  if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
706  $status = self::SEC_REAUTH;
707  }
708  } else {
709  $timeSinceLogin = -1;
710 
711  $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
712  if ( isset( $pass[$operation] ) ) {
713  $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
714  } elseif ( isset( $pass['default'] ) ) {
715  $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
716  } else {
717  throw new \UnexpectedValueException(
718  '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
719  );
720  }
721  }
722 
723  \Hooks::run( 'SecuritySensitiveOperationStatus', [
724  &$status, $operation, $session, $timeSinceLogin
725  ] );
726 
727  // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
728  if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
729  $status = self::SEC_FAIL;
730  }
731 
732  $this->logger->info( __METHOD__ . ": $operation is $status" );
733 
734  return $status;
735  }
736 
743  public function userCanAuthenticate( $username ) {
744  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
745  if ( $provider->testUserCanAuthenticate( $username ) ) {
746  return true;
747  }
748  }
749  return false;
750  }
751 
766  public function normalizeUsername( $username ) {
767  $ret = [];
768  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
769  $normalized = $provider->providerNormalizeUsername( $username );
770  if ( $normalized !== null ) {
771  $ret[$normalized] = true;
772  }
773  }
774  return array_keys( $ret );
775  }
776 
791  public function revokeAccessForUser( $username ) {
792  $this->logger->info( 'Revoking access for {user}', [
793  'user' => $username,
794  ] );
795  $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
796  }
797 
807  public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
808  $any = false;
809  $providers = $this->getPrimaryAuthenticationProviders() +
811  foreach ( $providers as $provider ) {
812  $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
813  if ( !$status->isGood() ) {
814  return Status::wrap( $status );
815  }
816  $any = $any || $status->value !== 'ignored';
817  }
818  if ( !$any ) {
819  $status = Status::newGood( 'ignored' );
820  $status->warning( 'authmanager-change-not-supported' );
821  return $status;
822  }
823  return Status::newGood();
824  }
825 
838  $this->logger->info( 'Changing authentication data for {user} class {what}', [
839  'user' => is_string( $req->username ) ? $req->username : '<no name>',
840  'what' => get_class( $req ),
841  ] );
842 
843  $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
844 
845  // When the main account's authentication data is changed, invalidate
846  // all BotPasswords too.
848  }
849 
861  public function canCreateAccounts() {
862  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
863  switch ( $provider->accountCreationType() ) {
866  return true;
867  }
868  }
869  return false;
870  }
871 
879  if ( !$this->canCreateAccounts() ) {
880  return Status::newFatal( 'authmanager-create-disabled' );
881  }
882 
883  if ( $this->userExists( $username, $flags ) ) {
884  return Status::newFatal( 'userexists' );
885  }
886 
887  $user = User::newFromName( $username, 'creatable' );
888  if ( !is_object( $user ) ) {
889  return Status::newFatal( 'noname' );
890  } else {
891  $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
892  if ( $user->getId() !== 0 ) {
893  return Status::newFatal( 'userexists' );
894  }
895  }
896 
897  // Denied by providers?
898  $providers = $this->getPreAuthenticationProviders() +
901  foreach ( $providers as $provider ) {
902  $status = $provider->testUserForCreation( $user, false );
903  if ( !$status->isGood() ) {
904  return Status::wrap( $status );
905  }
906  }
907 
908  return Status::newGood();
909  }
910 
916  public function checkAccountCreatePermissions( User $creator ) {
917  // Wiki is read-only?
918  if ( wfReadOnly() ) {
919  return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
920  }
921 
922  // This is awful, this permission check really shouldn't go through Title.
923  $permErrors = \SpecialPage::getTitleFor( 'CreateAccount' )
924  ->getUserPermissionsErrors( 'createaccount', $creator, 'secure' );
925  if ( $permErrors ) {
927  foreach ( $permErrors as $args ) {
928  call_user_func_array( [ $status, 'fatal' ], $args );
929  }
930  return $status;
931  }
932 
933  $block = $creator->isBlockedFromCreateAccount();
934  if ( $block ) {
935  $errorParams = [
936  $block->getTarget(),
937  $block->mReason ?: wfMessage( 'blockednoreason' )->text(),
938  $block->getByName()
939  ];
940 
941  if ( $block->getType() === \Block::TYPE_RANGE ) {
942  $errorMessage = 'cantcreateaccount-range-text';
943  $errorParams[] = $this->getRequest()->getIP();
944  } else {
945  $errorMessage = 'cantcreateaccount-text';
946  }
947 
948  return Status::newFatal( wfMessage( $errorMessage, $errorParams ) );
949  }
950 
951  $ip = $this->getRequest()->getIP();
952  if ( $creator->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
953  return Status::newFatal( 'sorbs_create_account_reason' );
954  }
955 
956  return Status::newGood();
957  }
958 
978  public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
979  $session = $this->request->getSession();
980  if ( !$this->canCreateAccounts() ) {
981  // Caller should have called canCreateAccounts()
982  $session->remove( 'AuthManager::accountCreationState' );
983  throw new \LogicException( 'Account creation is not possible' );
984  }
985 
986  try {
988  } catch ( \UnexpectedValueException $ex ) {
989  $username = null;
990  }
991  if ( $username === null ) {
992  $this->logger->debug( __METHOD__ . ': No username provided' );
993  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
994  }
995 
996  // Permissions check
997  $status = $this->checkAccountCreatePermissions( $creator );
998  if ( !$status->isGood() ) {
999  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1000  'user' => $username,
1001  'creator' => $creator->getName(),
1002  'reason' => $status->getWikiText( null, null, 'en' )
1003  ] );
1004  return AuthenticationResponse::newFail( $status->getMessage() );
1005  }
1006 
1008  if ( !$status->isGood() ) {
1009  $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1010  'user' => $username,
1011  'creator' => $creator->getName(),
1012  'reason' => $status->getWikiText( null, null, 'en' )
1013  ] );
1014  return AuthenticationResponse::newFail( $status->getMessage() );
1015  }
1016 
1017  $user = User::newFromName( $username, 'creatable' );
1018  foreach ( $reqs as $req ) {
1019  $req->username = $username;
1020  $req->returnToUrl = $returnToUrl;
1021  if ( $req instanceof UserDataAuthenticationRequest ) {
1022  $status = $req->populateUser( $user );
1023  if ( !$status->isGood() ) {
1025  $session->remove( 'AuthManager::accountCreationState' );
1026  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1027  'user' => $user->getName(),
1028  'creator' => $creator->getName(),
1029  'reason' => $status->getWikiText( null, null, 'en' ),
1030  ] );
1031  return AuthenticationResponse::newFail( $status->getMessage() );
1032  }
1033  }
1034  }
1035 
1036  $this->removeAuthenticationSessionData( null );
1037 
1038  $state = [
1039  'username' => $username,
1040  'userid' => 0,
1041  'creatorid' => $creator->getId(),
1042  'creatorname' => $creator->getName(),
1043  'reqs' => $reqs,
1044  'returnToUrl' => $returnToUrl,
1045  'primary' => null,
1046  'primaryResponse' => null,
1047  'secondary' => [],
1048  'continueRequests' => [],
1049  'maybeLink' => [],
1050  'ranPreTests' => false,
1051  ];
1052 
1053  // Special case: converting a login to an account creation
1056  );
1057  if ( $req ) {
1058  $state['maybeLink'] = $req->maybeLink;
1059 
1060  if ( $req->createRequest ) {
1061  $reqs[] = $req->createRequest;
1062  $state['reqs'][] = $req->createRequest;
1063  }
1064  }
1065 
1066  $session->setSecret( 'AuthManager::accountCreationState', $state );
1067  $session->persist();
1068 
1069  return $this->continueAccountCreation( $reqs );
1070  }
1071 
1077  public function continueAccountCreation( array $reqs ) {
1078  $session = $this->request->getSession();
1079  try {
1080  if ( !$this->canCreateAccounts() ) {
1081  // Caller should have called canCreateAccounts()
1082  $session->remove( 'AuthManager::accountCreationState' );
1083  throw new \LogicException( 'Account creation is not possible' );
1084  }
1085 
1086  $state = $session->getSecret( 'AuthManager::accountCreationState' );
1087  if ( !is_array( $state ) ) {
1089  wfMessage( 'authmanager-create-not-in-progress' )
1090  );
1091  }
1092  $state['continueRequests'] = [];
1093 
1094  // Step 0: Prepare and validate the input
1095 
1096  $user = User::newFromName( $state['username'], 'creatable' );
1097  if ( !is_object( $user ) ) {
1098  $session->remove( 'AuthManager::accountCreationState' );
1099  $this->logger->debug( __METHOD__ . ': Invalid username', [
1100  'user' => $state['username'],
1101  ] );
1102  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1103  }
1104 
1105  if ( $state['creatorid'] ) {
1106  $creator = User::newFromId( $state['creatorid'] );
1107  } else {
1108  $creator = new User;
1109  $creator->setName( $state['creatorname'] );
1110  }
1111 
1112  // Avoid account creation races on double submissions
1114  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1115  if ( !$lock ) {
1116  // Don't clear AuthManager::accountCreationState for this code
1117  // path because the process that won the race owns it.
1118  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1119  'user' => $user->getName(),
1120  'creator' => $creator->getName(),
1121  ] );
1122  return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1123  }
1124 
1125  // Permissions check
1126  $status = $this->checkAccountCreatePermissions( $creator );
1127  if ( !$status->isGood() ) {
1128  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1129  'user' => $user->getName(),
1130  'creator' => $creator->getName(),
1131  'reason' => $status->getWikiText( null, null, 'en' )
1132  ] );
1133  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1134  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1135  $session->remove( 'AuthManager::accountCreationState' );
1136  return $ret;
1137  }
1138 
1139  // Load from master for existence check
1140  $user->load( User::READ_LOCKING );
1141 
1142  if ( $state['userid'] === 0 ) {
1143  if ( $user->getId() != 0 ) {
1144  $this->logger->debug( __METHOD__ . ': User exists locally', [
1145  'user' => $user->getName(),
1146  'creator' => $creator->getName(),
1147  ] );
1148  $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1149  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1150  $session->remove( 'AuthManager::accountCreationState' );
1151  return $ret;
1152  }
1153  } else {
1154  if ( $user->getId() == 0 ) {
1155  $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1156  'user' => $user->getName(),
1157  'creator' => $creator->getName(),
1158  'expected_id' => $state['userid'],
1159  ] );
1160  throw new \UnexpectedValueException(
1161  "User \"{$state['username']}\" should exist now, but doesn't!"
1162  );
1163  }
1164  if ( $user->getId() != $state['userid'] ) {
1165  $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1166  'user' => $user->getName(),
1167  'creator' => $creator->getName(),
1168  'expected_id' => $state['userid'],
1169  'actual_id' => $user->getId(),
1170  ] );
1171  throw new \UnexpectedValueException(
1172  "User \"{$state['username']}\" exists, but " .
1173  "ID {$user->getId()} != {$state['userid']}!"
1174  );
1175  }
1176  }
1177  foreach ( $state['reqs'] as $req ) {
1178  if ( $req instanceof UserDataAuthenticationRequest ) {
1179  $status = $req->populateUser( $user );
1180  if ( !$status->isGood() ) {
1181  // This should never happen...
1183  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1184  'user' => $user->getName(),
1185  'creator' => $creator->getName(),
1186  'reason' => $status->getWikiText( null, null, 'en' ),
1187  ] );
1188  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1189  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1190  $session->remove( 'AuthManager::accountCreationState' );
1191  return $ret;
1192  }
1193  }
1194  }
1195 
1196  foreach ( $reqs as $req ) {
1197  $req->returnToUrl = $state['returnToUrl'];
1198  $req->username = $state['username'];
1199  }
1200 
1201  // Run pre-creation tests, if we haven't already
1202  if ( !$state['ranPreTests'] ) {
1203  $providers = $this->getPreAuthenticationProviders() +
1206  foreach ( $providers as $id => $provider ) {
1207  $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1208  if ( !$status->isGood() ) {
1209  $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1210  'user' => $user->getName(),
1211  'creator' => $creator->getName(),
1212  ] );
1214  Status::wrap( $status )->getMessage()
1215  );
1216  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1217  $session->remove( 'AuthManager::accountCreationState' );
1218  return $ret;
1219  }
1220  }
1221 
1222  $state['ranPreTests'] = true;
1223  }
1224 
1225  // Step 1: Choose a primary authentication provider and call it until it succeeds.
1226 
1227  if ( $state['primary'] === null ) {
1228  // We haven't picked a PrimaryAuthenticationProvider yet
1229  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1230  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1231  continue;
1232  }
1233  $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1234  switch ( $res->status ) {
1236  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1237  'user' => $user->getName(),
1238  'creator' => $creator->getName(),
1239  ] );
1240  $state['primary'] = $id;
1241  $state['primaryResponse'] = $res;
1242  break 2;
1244  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1245  'user' => $user->getName(),
1246  'creator' => $creator->getName(),
1247  ] );
1248  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1249  $session->remove( 'AuthManager::accountCreationState' );
1250  return $res;
1252  // Continue loop
1253  break;
1256  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1257  'user' => $user->getName(),
1258  'creator' => $creator->getName(),
1259  ] );
1260  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1261  $state['primary'] = $id;
1262  $state['continueRequests'] = $res->neededRequests;
1263  $session->setSecret( 'AuthManager::accountCreationState', $state );
1264  return $res;
1265 
1266  // @codeCoverageIgnoreStart
1267  default:
1268  throw new \DomainException(
1269  get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1270  );
1271  // @codeCoverageIgnoreEnd
1272  }
1273  }
1274  if ( $state['primary'] === null ) {
1275  $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1276  'user' => $user->getName(),
1277  'creator' => $creator->getName(),
1278  ] );
1280  wfMessage( 'authmanager-create-no-primary' )
1281  );
1282  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1283  $session->remove( 'AuthManager::accountCreationState' );
1284  return $ret;
1285  }
1286  } elseif ( $state['primaryResponse'] === null ) {
1287  $provider = $this->getAuthenticationProvider( $state['primary'] );
1288  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1289  // Configuration changed? Force them to start over.
1290  // @codeCoverageIgnoreStart
1292  wfMessage( 'authmanager-create-not-in-progress' )
1293  );
1294  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1295  $session->remove( 'AuthManager::accountCreationState' );
1296  return $ret;
1297  // @codeCoverageIgnoreEnd
1298  }
1299  $id = $provider->getUniqueId();
1300  $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1301  switch ( $res->status ) {
1303  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1304  'user' => $user->getName(),
1305  'creator' => $creator->getName(),
1306  ] );
1307  $state['primaryResponse'] = $res;
1308  break;
1310  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1311  'user' => $user->getName(),
1312  'creator' => $creator->getName(),
1313  ] );
1314  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1315  $session->remove( 'AuthManager::accountCreationState' );
1316  return $res;
1319  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1320  'user' => $user->getName(),
1321  'creator' => $creator->getName(),
1322  ] );
1323  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1324  $state['continueRequests'] = $res->neededRequests;
1325  $session->setSecret( 'AuthManager::accountCreationState', $state );
1326  return $res;
1327  default:
1328  throw new \DomainException(
1329  get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1330  );
1331  }
1332  }
1333 
1334  // Step 2: Primary authentication succeeded, create the User object
1335  // and add the user locally.
1336 
1337  if ( $state['userid'] === 0 ) {
1338  $this->logger->info( 'Creating user {user} during account creation', [
1339  'user' => $user->getName(),
1340  'creator' => $creator->getName(),
1341  ] );
1342  $status = $user->addToDatabase();
1343  if ( !$status->isOk() ) {
1344  // @codeCoverageIgnoreStart
1345  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1346  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1347  $session->remove( 'AuthManager::accountCreationState' );
1348  return $ret;
1349  // @codeCoverageIgnoreEnd
1350  }
1351  $this->setDefaultUserOptions( $user, $creator->isAnon() );
1352  \Hooks::run( 'LocalUserCreated', [ $user, false ] );
1353  $user->saveSettings();
1354  $state['userid'] = $user->getId();
1355 
1356  // Update user count
1357  \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1358 
1359  // Watch user's userpage and talk page
1360  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1361 
1362  // Inform the provider
1363  $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1364 
1365  // Log the creation
1366  if ( $this->config->get( 'NewUserLog' ) ) {
1367  $isAnon = $creator->isAnon();
1368  $logEntry = new \ManualLogEntry(
1369  'newusers',
1370  $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1371  );
1372  $logEntry->setPerformer( $isAnon ? $user : $creator );
1373  $logEntry->setTarget( $user->getUserPage() );
1376  );
1377  $logEntry->setComment( $req ? $req->reason : '' );
1378  $logEntry->setParameters( [
1379  '4::userid' => $user->getId(),
1380  ] );
1381  $logid = $logEntry->insert();
1382  $logEntry->publish( $logid );
1383  }
1384  }
1385 
1386  // Step 3: Iterate over all the secondary authentication providers.
1387 
1388  $beginReqs = $state['reqs'];
1389 
1390  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1391  if ( !isset( $state['secondary'][$id] ) ) {
1392  // This provider isn't started yet, so we pass it the set
1393  // of reqs from beginAuthentication instead of whatever
1394  // might have been used by a previous provider in line.
1395  $func = 'beginSecondaryAccountCreation';
1396  $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1397  } elseif ( !$state['secondary'][$id] ) {
1398  $func = 'continueSecondaryAccountCreation';
1399  $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1400  } else {
1401  continue;
1402  }
1403  switch ( $res->status ) {
1405  $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1406  'user' => $user->getName(),
1407  'creator' => $creator->getName(),
1408  ] );
1409  // fall through
1411  $state['secondary'][$id] = true;
1412  break;
1415  $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1416  'user' => $user->getName(),
1417  'creator' => $creator->getName(),
1418  ] );
1419  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1420  $state['secondary'][$id] = false;
1421  $state['continueRequests'] = $res->neededRequests;
1422  $session->setSecret( 'AuthManager::accountCreationState', $state );
1423  return $res;
1425  throw new \DomainException(
1426  get_class( $provider ) . "::{$func}() returned $res->status." .
1427  ' Secondary providers are not allowed to fail account creation, that' .
1428  ' should have been done via testForAccountCreation().'
1429  );
1430  // @codeCoverageIgnoreStart
1431  default:
1432  throw new \DomainException(
1433  get_class( $provider ) . "::{$func}() returned $res->status"
1434  );
1435  // @codeCoverageIgnoreEnd
1436  }
1437  }
1438 
1439  $id = $user->getId();
1440  $name = $user->getName();
1441  $req = new CreatedAccountAuthenticationRequest( $id, $name );
1443  $ret->loginRequest = $req;
1444  $this->createdAccountAuthenticationRequests[] = $req;
1445 
1446  $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1447  'user' => $user->getName(),
1448  'creator' => $creator->getName(),
1449  ] );
1450 
1451  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1452  $session->remove( 'AuthManager::accountCreationState' );
1453  $this->removeAuthenticationSessionData( null );
1454  return $ret;
1455  } catch ( \Exception $ex ) {
1456  $session->remove( 'AuthManager::accountCreationState' );
1457  throw $ex;
1458  }
1459  }
1460 
1469  public function autoCreateUser( User $user, $source, $login = true ) {
1470  if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1472  ) {
1473  throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1474  }
1475 
1476  $username = $user->getName();
1477 
1478  // Try the local user from the slave DB
1479  $localId = User::idFromName( $username );
1481 
1482  // Fetch the user ID from the master, so that we don't try to create the user
1483  // when they already exist, due to replication lag
1484  // @codeCoverageIgnoreStart
1485  if ( !$localId && wfGetLB()->getReaderIndex() != 0 ) {
1488  }
1489  // @codeCoverageIgnoreEnd
1490 
1491  if ( $localId ) {
1492  $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1493  'username' => $username,
1494  ] );
1495  $user->setId( $localId );
1496  $user->loadFromId( $flags );
1497  if ( $login ) {
1498  $this->setSessionDataForUser( $user );
1499  }
1501  $status->warning( 'userexists' );
1502  return $status;
1503  }
1504 
1505  // Wiki is read-only?
1506  if ( wfReadOnly() ) {
1507  $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
1508  'username' => $username,
1509  'reason' => wfReadOnlyReason(),
1510  ] );
1511  $user->setId( 0 );
1512  $user->loadFromId();
1513  return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
1514  }
1515 
1516  // Check the session, if we tried to create this user already there's
1517  // no point in retrying.
1518  $session = $this->request->getSession();
1519  if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1520  $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1521  'username' => $username,
1522  'sessionid' => $session->getId(),
1523  ] );
1524  $user->setId( 0 );
1525  $user->loadFromId();
1526  $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1527  if ( $reason instanceof StatusValue ) {
1528  return Status::wrap( $reason );
1529  } else {
1530  return Status::newFatal( $reason );
1531  }
1532  }
1533 
1534  // Is the username creatable?
1535  if ( !User::isCreatableName( $username ) ) {
1536  $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1537  'username' => $username,
1538  ] );
1539  $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 );
1540  $user->setId( 0 );
1541  $user->loadFromId();
1542  return Status::newFatal( 'noname' );
1543  }
1544 
1545  // Is the IP user able to create accounts?
1546  $anon = new User;
1547  if ( !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' ) ) {
1548  $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1549  'username' => $username,
1550  'ip' => $anon->getName(),
1551  ] );
1552  $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 );
1553  $session->persist();
1554  $user->setId( 0 );
1555  $user->loadFromId();
1556  return Status::newFatal( 'authmanager-autocreate-noperm' );
1557  }
1558 
1559  // Avoid account creation races on double submissions
1561  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1562  if ( !$lock ) {
1563  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1564  'user' => $username,
1565  ] );
1566  $user->setId( 0 );
1567  $user->loadFromId();
1568  return Status::newFatal( 'usernameinprogress' );
1569  }
1570 
1571  // Denied by providers?
1572  $providers = $this->getPreAuthenticationProviders() +
1575  foreach ( $providers as $provider ) {
1576  $status = $provider->testUserForCreation( $user, $source );
1577  if ( !$status->isGood() ) {
1578  $ret = Status::wrap( $status );
1579  $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1580  'username' => $username,
1581  'reason' => $ret->getWikiText( null, null, 'en' ),
1582  ] );
1583  $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 );
1584  $user->setId( 0 );
1585  $user->loadFromId();
1586  return $ret;
1587  }
1588  }
1589 
1590  // Ignore warnings about master connections/writes...hard to avoid here
1591  \Profiler::instance()->getTransactionProfiler()->resetExpectations();
1592 
1593  $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1594  if ( $cache->get( $backoffKey ) ) {
1595  $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1596  'username' => $username,
1597  ] );
1598  $user->setId( 0 );
1599  $user->loadFromId();
1600  return Status::newFatal( 'authmanager-autocreate-exception' );
1601  }
1602 
1603  // Checks passed, create the user...
1604  $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
1605  $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1606  'username' => $username,
1607  'from' => $from,
1608  ] );
1609 
1610  try {
1611  $status = $user->addToDatabase();
1612  if ( !$status->isOk() ) {
1613  // double-check for a race condition (T70012)
1615  if ( $localId ) {
1616  $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1617  'username' => $username,
1618  ] );
1619  $user->setId( $localId );
1620  $user->loadFromId( User::READ_LATEST );
1621  if ( $login ) {
1622  $this->setSessionDataForUser( $user );
1623  }
1625  $status->warning( 'userexists' );
1626  } else {
1627  $this->logger->error( __METHOD__ . ': {username} failed with message {message}', [
1628  'username' => $username,
1629  'message' => $status->getWikiText( null, null, 'en' )
1630  ] );
1631  $user->setId( 0 );
1632  $user->loadFromId();
1633  }
1634  return $status;
1635  }
1636  } catch ( \Exception $ex ) {
1637  $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1638  'username' => $username,
1639  'exception' => $ex,
1640  ] );
1641  // Do not keep throwing errors for a while
1642  $cache->set( $backoffKey, 1, 600 );
1643  // Bubble up error; which should normally trigger DB rollbacks
1644  throw $ex;
1645  }
1646 
1647  $this->setDefaultUserOptions( $user, true );
1648 
1649  // Inform the providers
1650  $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1651 
1652  \Hooks::run( 'AuthPluginAutoCreate', [ $user ], '1.27' );
1653  \Hooks::run( 'LocalUserCreated', [ $user, true ] );
1654  $user->saveSettings();
1655 
1656  // Update user count
1657  \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1658 
1659  // Watch user's userpage and talk page
1660  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1661 
1662  // Log the creation
1663  if ( $this->config->get( 'NewUserLog' ) ) {
1664  $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1665  $logEntry->setPerformer( $user );
1666  $logEntry->setTarget( $user->getUserPage() );
1667  $logEntry->setComment( '' );
1668  $logEntry->setParameters( [
1669  '4::userid' => $user->getId(),
1670  ] );
1671  $logid = $logEntry->insert();
1672  }
1673 
1674  if ( $login ) {
1675  $this->setSessionDataForUser( $user );
1676  }
1677 
1678  return Status::newGood();
1679  }
1680 
1692  public function canLinkAccounts() {
1693  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1694  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1695  return true;
1696  }
1697  }
1698  return false;
1699  }
1700 
1710  public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1711  $session = $this->request->getSession();
1712  $session->remove( 'AuthManager::accountLinkState' );
1713 
1714  if ( !$this->canLinkAccounts() ) {
1715  // Caller should have called canLinkAccounts()
1716  throw new \LogicException( 'Account linking is not possible' );
1717  }
1718 
1719  if ( $user->getId() === 0 ) {
1720  if ( !User::isUsableName( $user->getName() ) ) {
1721  $msg = wfMessage( 'noname' );
1722  } else {
1723  $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1724  }
1725  return AuthenticationResponse::newFail( $msg );
1726  }
1727  foreach ( $reqs as $req ) {
1728  $req->username = $user->getName();
1729  $req->returnToUrl = $returnToUrl;
1730  }
1731 
1732  $this->removeAuthenticationSessionData( null );
1733 
1734  $providers = $this->getPreAuthenticationProviders();
1735  foreach ( $providers as $id => $provider ) {
1736  $status = $provider->testForAccountLink( $user );
1737  if ( !$status->isGood() ) {
1738  $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1739  'user' => $user->getName(),
1740  ] );
1742  Status::wrap( $status )->getMessage()
1743  );
1744  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1745  return $ret;
1746  }
1747  }
1748 
1749  $state = [
1750  'username' => $user->getName(),
1751  'userid' => $user->getId(),
1752  'returnToUrl' => $returnToUrl,
1753  'primary' => null,
1754  'continueRequests' => [],
1755  ];
1756 
1757  $providers = $this->getPrimaryAuthenticationProviders();
1758  foreach ( $providers as $id => $provider ) {
1759  if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1760  continue;
1761  }
1762 
1763  $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1764  switch ( $res->status ) {
1766  $this->logger->info( "Account linked to {user} by $id", [
1767  'user' => $user->getName(),
1768  ] );
1769  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1770  return $res;
1771 
1773  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1774  'user' => $user->getName(),
1775  ] );
1776  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1777  return $res;
1778 
1780  // Continue loop
1781  break;
1782 
1785  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1786  'user' => $user->getName(),
1787  ] );
1788  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1789  $state['primary'] = $id;
1790  $state['continueRequests'] = $res->neededRequests;
1791  $session->setSecret( 'AuthManager::accountLinkState', $state );
1792  $session->persist();
1793  return $res;
1794 
1795  // @codeCoverageIgnoreStart
1796  default:
1797  throw new \DomainException(
1798  get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1799  );
1800  // @codeCoverageIgnoreEnd
1801  }
1802  }
1803 
1804  $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1805  'user' => $user->getName(),
1806  ] );
1808  wfMessage( 'authmanager-link-no-primary' )
1809  );
1810  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1811  return $ret;
1812  }
1813 
1819  public function continueAccountLink( array $reqs ) {
1820  $session = $this->request->getSession();
1821  try {
1822  if ( !$this->canLinkAccounts() ) {
1823  // Caller should have called canLinkAccounts()
1824  $session->remove( 'AuthManager::accountLinkState' );
1825  throw new \LogicException( 'Account linking is not possible' );
1826  }
1827 
1828  $state = $session->getSecret( 'AuthManager::accountLinkState' );
1829  if ( !is_array( $state ) ) {
1831  wfMessage( 'authmanager-link-not-in-progress' )
1832  );
1833  }
1834  $state['continueRequests'] = [];
1835 
1836  // Step 0: Prepare and validate the input
1837 
1838  $user = User::newFromName( $state['username'], 'usable' );
1839  if ( !is_object( $user ) ) {
1840  $session->remove( 'AuthManager::accountLinkState' );
1841  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1842  }
1843  if ( $user->getId() != $state['userid'] ) {
1844  throw new \UnexpectedValueException(
1845  "User \"{$state['username']}\" is valid, but " .
1846  "ID {$user->getId()} != {$state['userid']}!"
1847  );
1848  }
1849 
1850  foreach ( $reqs as $req ) {
1851  $req->username = $state['username'];
1852  $req->returnToUrl = $state['returnToUrl'];
1853  }
1854 
1855  // Step 1: Call the primary again until it succeeds
1856 
1857  $provider = $this->getAuthenticationProvider( $state['primary'] );
1858  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1859  // Configuration changed? Force them to start over.
1860  // @codeCoverageIgnoreStart
1862  wfMessage( 'authmanager-link-not-in-progress' )
1863  );
1864  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1865  $session->remove( 'AuthManager::accountLinkState' );
1866  return $ret;
1867  // @codeCoverageIgnoreEnd
1868  }
1869  $id = $provider->getUniqueId();
1870  $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1871  switch ( $res->status ) {
1873  $this->logger->info( "Account linked to {user} by $id", [
1874  'user' => $user->getName(),
1875  ] );
1876  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1877  $session->remove( 'AuthManager::accountLinkState' );
1878  return $res;
1880  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1881  'user' => $user->getName(),
1882  ] );
1883  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1884  $session->remove( 'AuthManager::accountLinkState' );
1885  return $res;
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['continueRequests'] = $res->neededRequests;
1893  $session->setSecret( 'AuthManager::accountLinkState', $state );
1894  return $res;
1895  default:
1896  throw new \DomainException(
1897  get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
1898  );
1899  }
1900  } catch ( \Exception $ex ) {
1901  $session->remove( 'AuthManager::accountLinkState' );
1902  throw $ex;
1903  }
1904  }
1905 
1931  public function getAuthenticationRequests( $action, User $user = null ) {
1932  $options = [];
1933  $providerAction = $action;
1934 
1935  // Figure out which providers to query
1936  switch ( $action ) {
1937  case self::ACTION_LOGIN:
1938  case self::ACTION_CREATE:
1939  $providers = $this->getPreAuthenticationProviders() +
1942  break;
1943 
1944  case self::ACTION_LOGIN_CONTINUE:
1945  $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
1946  return is_array( $state ) ? $state['continueRequests'] : [];
1947 
1948  case self::ACTION_CREATE_CONTINUE:
1949  $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
1950  return is_array( $state ) ? $state['continueRequests'] : [];
1951 
1952  case self::ACTION_LINK:
1953  $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
1954  return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
1955  } );
1956  break;
1957 
1958  case self::ACTION_UNLINK:
1959  $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
1960  return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
1961  } );
1962 
1963  // To providers, unlink and remove are identical.
1964  $providerAction = self::ACTION_REMOVE;
1965  break;
1966 
1967  case self::ACTION_LINK_CONTINUE:
1968  $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
1969  return is_array( $state ) ? $state['continueRequests'] : [];
1970 
1971  case self::ACTION_CHANGE:
1972  case self::ACTION_REMOVE:
1973  $providers = $this->getPrimaryAuthenticationProviders() +
1975  break;
1976 
1977  // @codeCoverageIgnoreStart
1978  default:
1979  throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
1980  }
1981  // @codeCoverageIgnoreEnd
1982 
1983  return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
1984  }
1985 
1996  $providerAction, array $options, array $providers, User $user = null
1997  ) {
1998  $user = $user ?: \RequestContext::getMain()->getUser();
1999  $options['username'] = $user->isAnon() ? null : $user->getName();
2000 
2001  // Query them and merge results
2002  $reqs = [];
2003  foreach ( $providers as $provider ) {
2004  $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2005  foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2006  $id = $req->getUniqueId();
2007 
2008  // If a required request if from a Primary, mark it as "primary-required" instead
2009  if ( $isPrimary ) {
2010  if ( $req->required ) {
2012  }
2013  }
2014 
2015  if (
2016  !isset( $reqs[$id] )
2017  || $req->required === AuthenticationRequest::REQUIRED
2018  || $reqs[$id] === AuthenticationRequest::OPTIONAL
2019  ) {
2020  $reqs[$id] = $req;
2021  }
2022  }
2023  }
2024 
2025  // AuthManager has its own req for some actions
2026  switch ( $providerAction ) {
2027  case self::ACTION_LOGIN:
2028  $reqs[] = new RememberMeAuthenticationRequest;
2029  break;
2030 
2031  case self::ACTION_CREATE:
2032  $reqs[] = new UsernameAuthenticationRequest;
2033  $reqs[] = new UserDataAuthenticationRequest;
2034  if ( $options['username'] !== null ) {
2036  $options['username'] = null; // Don't fill in the username below
2037  }
2038  break;
2039  }
2040 
2041  // Fill in reqs data
2042  $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2043 
2044  // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2045  if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2046  $reqs = array_filter( $reqs, function ( $req ) {
2047  return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2048  } );
2049  }
2050 
2051  return array_values( $reqs );
2052  }
2053 
2061  private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2062  foreach ( $reqs as $req ) {
2063  if ( !$req->action || $forceAction ) {
2064  $req->action = $action;
2065  }
2066  if ( $req->username === null ) {
2067  $req->username = $username;
2068  }
2069  }
2070  }
2071 
2079  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2080  if ( $provider->testUserExists( $username, $flags ) ) {
2081  return true;
2082  }
2083  }
2084 
2085  return false;
2086  }
2087 
2099  public function allowsPropertyChange( $property ) {
2100  $providers = $this->getPrimaryAuthenticationProviders() +
2102  foreach ( $providers as $provider ) {
2103  if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2104  return false;
2105  }
2106  }
2107  return true;
2108  }
2109 
2118  public function getAuthenticationProvider( $id ) {
2119  // Fast version
2120  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2121  return $this->allAuthenticationProviders[$id];
2122  }
2123 
2124  // Slow version: instantiate each kind and check
2125  $providers = $this->getPrimaryAuthenticationProviders();
2126  if ( isset( $providers[$id] ) ) {
2127  return $providers[$id];
2128  }
2129  $providers = $this->getSecondaryAuthenticationProviders();
2130  if ( isset( $providers[$id] ) ) {
2131  return $providers[$id];
2132  }
2133  $providers = $this->getPreAuthenticationProviders();
2134  if ( isset( $providers[$id] ) ) {
2135  return $providers[$id];
2136  }
2137 
2138  return null;
2139  }
2140 
2154  public function setAuthenticationSessionData( $key, $data ) {
2155  $session = $this->request->getSession();
2156  $arr = $session->getSecret( 'authData' );
2157  if ( !is_array( $arr ) ) {
2158  $arr = [];
2159  }
2160  $arr[$key] = $data;
2161  $session->setSecret( 'authData', $arr );
2162  }
2163 
2171  public function getAuthenticationSessionData( $key, $default = null ) {
2172  $arr = $this->request->getSession()->getSecret( 'authData' );
2173  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2174  return $arr[$key];
2175  } else {
2176  return $default;
2177  }
2178  }
2179 
2186  $session = $this->request->getSession();
2187  if ( $key === null ) {
2188  $session->remove( 'authData' );
2189  } else {
2190  $arr = $session->getSecret( 'authData' );
2191  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2192  unset( $arr[$key] );
2193  $session->setSecret( 'authData', $arr );
2194  }
2195  }
2196  }
2197 
2204  protected function providerArrayFromSpecs( $class, array $specs ) {
2205  $i = 0;
2206  foreach ( $specs as &$spec ) {
2207  $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2208  }
2209  unset( $spec );
2210  usort( $specs, function ( $a, $b ) {
2211  return ( (int)$a['sort'] ) - ( (int)$b['sort'] )
2212  ?: $a['sort2'] - $b['sort2'];
2213  } );
2214 
2215  $ret = [];
2216  foreach ( $specs as $spec ) {
2217  $provider = \ObjectFactory::getObjectFromSpec( $spec );
2218  if ( !$provider instanceof $class ) {
2219  throw new \RuntimeException(
2220  "Expected instance of $class, got " . get_class( $provider )
2221  );
2222  }
2223  $provider->setLogger( $this->logger );
2224  $provider->setManager( $this );
2225  $provider->setConfig( $this->config );
2226  $id = $provider->getUniqueId();
2227  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2228  throw new \RuntimeException(
2229  "Duplicate specifications for id $id (classes " .
2230  get_class( $provider ) . ' and ' .
2231  get_class( $this->allAuthenticationProviders[$id] ) . ')'
2232  );
2233  }
2234  $this->allAuthenticationProviders[$id] = $provider;
2235  $ret[$id] = $provider;
2236  }
2237  return $ret;
2238  }
2239 
2244  private function getConfiguration() {
2245  return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2246  }
2247 
2252  protected function getPreAuthenticationProviders() {
2253  if ( $this->preAuthenticationProviders === null ) {
2254  $conf = $this->getConfiguration();
2255  $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2256  PreAuthenticationProvider::class, $conf['preauth']
2257  );
2258  }
2260  }
2261 
2266  protected function getPrimaryAuthenticationProviders() {
2267  if ( $this->primaryAuthenticationProviders === null ) {
2268  $conf = $this->getConfiguration();
2269  $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2270  PrimaryAuthenticationProvider::class, $conf['primaryauth']
2271  );
2272  }
2274  }
2275 
2281  if ( $this->secondaryAuthenticationProviders === null ) {
2282  $conf = $this->getConfiguration();
2283  $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2284  SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2285  );
2286  }
2288  }
2289 
2294  private function setSessionDataForUser( $user, $remember = null ) {
2295  $session = $this->request->getSession();
2296  $delay = $session->delaySave();
2297 
2298  $session->resetId();
2299  $session->resetAllTokens();
2300  if ( $session->canSetUser() ) {
2301  $session->setUser( $user );
2302  }
2303  if ( $remember !== null ) {
2304  $session->setRememberUser( $remember );
2305  }
2306  $session->set( 'AuthManager:lastAuthId', $user->getId() );
2307  $session->set( 'AuthManager:lastAuthTimestamp', time() );
2308  $session->persist();
2309 
2310  \ScopedCallback::consume( $delay );
2311 
2312  \Hooks::run( 'UserLoggedIn', [ $user ] );
2313  }
2314 
2319  private function setDefaultUserOptions( User $user, $useContextLang ) {
2321 
2322  $user->setToken();
2323 
2324  $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $wgContLang;
2325  $user->setOption( 'language', $lang->getPreferredVariant() );
2326 
2327  if ( $wgContLang->hasVariants() ) {
2328  $user->setOption( 'variant', $wgContLang->getPreferredVariant() );
2329  }
2330  }
2331 
2337  private function callMethodOnProviders( $which, $method, array $args ) {
2338  $providers = [];
2339  if ( $which & 1 ) {
2340  $providers += $this->getPreAuthenticationProviders();
2341  }
2342  if ( $which & 2 ) {
2343  $providers += $this->getPrimaryAuthenticationProviders();
2344  }
2345  if ( $which & 4 ) {
2346  $providers += $this->getSecondaryAuthenticationProviders();
2347  }
2348  foreach ( $providers as $provider ) {
2349  call_user_func_array( [ $provider, $method ], $args );
2350  }
2351  }
2352 
2356  public static function resetCache() {
2357  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2358  // @codeCoverageIgnoreStart
2359  throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2360  // @codeCoverageIgnoreEnd
2361  }
2362 
2363  self::$instance = null;
2364  }
2365 
2368 }
2369 
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:568
userExists($username, $flags=User::READ_NORMAL)
Determine whether a username exists.
This transfers state between the login and account creation flows.
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provdier.
the array() calling protocol came about after MediaWiki 1.4rc1.
static wrap($sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:79
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
static getObjectFromSpec($spec)
Instantiate an object based on a specification array.
$property
const ABSTAIN
Indicates that the authentication provider does not handle this request.
const TYPE_RANGE
Definition: Block.php:77
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
continueAuthentication(array $reqs)
Continue an authentication flow.
canCreateAccounts()
Determine whether accounts can be created.
isDnsBlacklisted($ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition: User.php:1653
saveSettings()
Save this user's settings into the database.
Definition: User.php:3765
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:75
userCanAuthenticate($username)
Determine whether a username can authenticate.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1798
static instance()
Singleton.
Definition: Profiler.php:60
changeAuthenticationData(AuthenticationRequest $req)
Change authentication data (e.g.
if(!isset($args[0])) $lang
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
Definition: AuthManager.php:64
static getInstance($channel)
Get a named logger instance from the currently configured logger factory.
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:895
$source
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
static newFromId($id)
Static factory method for creation from a given user ID.
Definition: User.php:591
getAuthenticationRequests($action, User $user=null)
Return the applicable list of AuthenticationRequests.
static getLocalClusterInstance()
Get the main cluster-local cache object.
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2548
continueAccountCreation(array $reqs)
Continue an account creation flow.
Authentication request for the reason given for account creation.
A helper class for throttling authentication attempts.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static newFatal($message)
Factory function for fatal errors.
Definition: Status.php:89
setToken($token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition: User.php:2589
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:2833
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2086
PreAuthenticationProvider[] $preAuthenticationProviders
Definition: AuthManager.php:92
CreatedAccountAuthenticationRequest[] $createdAccountAuthenticationRequests
A primary authentication provider determines which user is trying to log in.
if($line===false) $args
Definition: cdb.php:64
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
setAuthenticationSessionData($key, $data)
Store authentication in the current session.
$last
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4040
AuthenticationProvider[] $allAuthenticationProviders
Definition: AuthManager.php:89
wfGetLB($wiki=false)
Get a load balancer object.
Backwards-compatibility wrapper for AuthManager via $wgAuth.
wfReadOnly()
Check whether the wiki is in read-only mode.
static resetCache()
Reset the internal caching for unit testing.
static getMain()
Static methods.
canCreateAccount($username, $flags=User::READ_NORMAL)
Determine whether a particular account can be created.
canLinkAccounts()
Determine whether accounts can be linked.
This represents additional user data requested on the account creation form.
const FAIL
Indicates that the authentication failed.
const TYPE_LINK
Provider can link to existing accounts elsewhere.
securitySensitiveOperationStatus($operation)
Whether security-sensitive operations should proceed.
allowsPropertyChange($property)
Determine whether a user property should be allowed to be changed.
LoggerInterface $logger
Definition: AuthManager.php:86
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
static AuthManager null $instance
Definition: AuthManager.php:77
PrimaryAuthenticationProvider[] $primaryAuthenticationProviders
Definition: AuthManager.php:95
static singleton()
Get the global AuthManager.
static invalidateAllPasswordsForUser($username)
Invalidate all passwords for a user, by name.
$res
Definition: database.txt:21
Class for handling updates to the site_stats table.
const ACTION_CHANGE
Change a user's credentials.
Definition: AuthManager.php:60
callMethodOnProviders($which, $method, array $args)
const SEC_FAIL
Security-sensitive should not be performed.
Definition: AuthManager.php:71
const SEC_REAUTH
Security-sensitive operations should re-authenticate.
Definition: AuthManager.php:69
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
Definition: AuthManager.php:74
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock()-offset Set to overwrite offset parameter in $wgRequest set to ''to unsetoffset-wrap String Wrap the message in html(usually something like"&lt
revokeAccessForUser($username)
Revoke any authentication credentials for a user.
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
$cache
Definition: mcc.php:33
const IGNORE_USER_RIGHTS
Definition: User.php:84
$params
getConfiguration()
Get the configuration.
const REQUIRED
Indicates that the request is required for authentication to proceed.
Returned from account creation to allow for logging into the created account.
This is an authentication request added by AuthManager to show a "remember me" checkbox.
const PASS
Indicates that the authentication succeeded.
static callLegacyAuthPlugin($method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
static addUpdate(DeferrableUpdate $update, $type=self::POSTSEND)
Add an update to the deferred list.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
This serves as the entry point to the authentication system.
Definition: AuthManager.php:43
canAuthenticateNow()
Indicate whether user authentication is possible.
setLogger(LoggerInterface $logger)
AuthenticationRequest to ensure something with a username is present.
const TYPE_NONE
Provider cannot create or link to accounts.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
const ACTION_LINK
Link an existing user to a third-party account.
Definition: AuthManager.php:55
checkAccountCreatePermissions(User $creator)
Basic permissions checks on whether a user can create accounts.
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
normalizeUsername($username)
Provide normalized versions of the username for security checks.
const REDIRECT
Indicates that the authentication needs to be redirected to a third party to proceed.
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
forcePrimaryAuthenticationProviders(array $providers, $why)
Force certain PrimaryAuthenticationProviders.
String $action
Cache what action this request is.
Definition: MediaWiki.php:42
static getDefaultInstance()
removeAuthenticationSessionData($key)
Remove authentication data.
setId($v)
Set the user and reload all fields according to a given ID.
Definition: User.php:2077
beginAccountCreation(User $creator, array $reqs, $returnToUrl)
Start an account creation flow.
$from
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
this hook is for auditing only $req
Definition: hooks.txt:965
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:762
getAuthenticationProvider($id)
Get a provider by ID.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
getId()
Get the user's ID.
Definition: User.php:2061
providerArrayFromSpecs($class, array $specs)
Create an array of AuthenticationProviders from an array of ObjectFactory specs.
addToDatabase()
Add this existing user object to the database.
Definition: User.php:3935
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getPrimaryAuthenticationProviders()
Get the list of PrimaryAuthenticationProviders.
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:48
const ACTION_REMOVE
Remove a user's credentials.
Definition: AuthManager.php:62
getAuthenticationRequestsInternal($providerAction, array $options, array $providers, User $user=null)
Internal request lookup for self::getAuthenticationRequests.
static isCreatableName($name)
Usernames which fail to pass this function will be blocked from new account registrations, but may be used internally either by batch processes or by user accounts which have already been created.
Definition: User.php:932
autoCreateUser(User $user, $source, $login=true)
Auto-create an account, and log into that account.
getUserPage()
Get this user's personal page title.
Definition: User.php:4080
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:770
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
const ACTION_CREATE_CONTINUE
Continue a user creation process that was interrupted by the need for user input or communication wit...
Definition: AuthManager.php:53
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
continueAccountLink(array $reqs)
Continue an account linking flow.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1004
__construct(WebRequest $request, Config $config)
static consume(ScopedCallback &$sc=null)
Trigger a scoped callback and destroy it.
setDefaultUserOptions(User $user, $useContextLang)
const ACTION_CREATE
Create a new user.
Definition: AuthManager.php:50
wfMemcKey()
Make a cache key for the local wiki.
setSessionDataForUser($user, $remember=null)
getAuthenticationSessionData($key, $default=null)
Fetch authentication data from the current session.
loadFromId($flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition: User.php:425
const SEC_OK
Security-sensitive operations are ok.
Definition: AuthManager.php:67
addWatch($title, $checkRights=self::CHECK_USER_RIGHTS)
Watch an article.
Definition: User.php:3486
this hook is for auditing only etc instead of letting the login form give the generic error message that the account does not exist For when the account has been renamed or deleted or an array to pass a message key and parameters create2 Corresponds to logging log_action database field and which is displayed in the UI similar to $comment this hook should only be used to add variables that depend on the current page request
Definition: hooks.txt:1946
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
Definition: AuthManager.php:45
const ACTION_LINK_CONTINUE
Continue a user linking process that was interrupted by the need for user input or communication with...
Definition: AuthManager.php:58
fillRequests(array &$reqs, $action, $username, $forceAction=false)
Set values in an array of requests.
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
SecondaryAuthenticationProvider[] $secondaryAuthenticationProviders
Definition: AuthManager.php:98
static newGood($value=null)
Factory function for good results.
Definition: Status.php:101
This is a value object for authentication requests.
getSecondaryAuthenticationProviders()
Get the list of SecondaryAuthenticationProviders.
const UI
Indicates that the authentication needs further user input of some sort.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310