MediaWiki  master
AuthManager.php
Go to the documentation of this file.
1 <?php
24 namespace MediaWiki\Auth;
25 
26 use Config;
27 use Psr\Log\LoggerAwareInterface;
28 use Psr\Log\LoggerInterface;
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() {
109 
110  if ( $wgDisableAuthManager ) {
111  throw new \BadMethodCallException( '$wgDisableAuthManager is set' );
112  }
113 
114  if ( self::$instance === null ) {
115  self::$instance = new self(
116  \RequestContext::getMain()->getRequest(),
117  \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
118  );
119  }
120  return self::$instance;
121  }
122 
128  $this->request = $request;
129  $this->config = $config;
130  $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
131  }
132 
136  public function setLogger( LoggerInterface $logger ) {
137  $this->logger = $logger;
138  }
139 
143  public function getRequest() {
144  return $this->request;
145  }
146 
153  public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
154  $this->logger->warning( "Overriding AuthManager primary authn because $why" );
155 
156  if ( $this->primaryAuthenticationProviders !== null ) {
157  $this->logger->warning(
158  'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
159  );
160 
161  $this->allAuthenticationProviders = array_diff_key(
162  $this->allAuthenticationProviders,
163  $this->primaryAuthenticationProviders
164  );
165  $session = $this->request->getSession();
166  $session->remove( 'AuthManager::authnState' );
167  $session->remove( 'AuthManager::accountCreationState' );
168  $session->remove( 'AuthManager::accountLinkState' );
169  $this->createdAccountAuthenticationRequests = [];
170  }
171 
172  $this->primaryAuthenticationProviders = [];
173  foreach ( $providers as $provider ) {
174  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
175  throw new \RuntimeException(
176  'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
177  get_class( $provider )
178  );
179  }
180  $provider->setLogger( $this->logger );
181  $provider->setManager( $this );
182  $provider->setConfig( $this->config );
183  $id = $provider->getUniqueId();
184  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
185  throw new \RuntimeException(
186  "Duplicate specifications for id $id (classes " .
187  get_class( $provider ) . ' and ' .
188  get_class( $this->allAuthenticationProviders[$id] ) . ')'
189  );
190  }
191  $this->allAuthenticationProviders[$id] = $provider;
192  $this->primaryAuthenticationProviders[$id] = $provider;
193  }
194  }
195 
205  public static function callLegacyAuthPlugin( $method, array $params, $return = null ) {
206  global $wgAuth;
207 
208  if ( $wgAuth && !$wgAuth instanceof AuthManagerAuthPlugin ) {
209  return call_user_func_array( [ $wgAuth, $method ], $params );
210  } else {
211  return $return;
212  }
213  }
214 
228  public function canAuthenticateNow() {
229  return $this->request->getSession()->canSetUser();
230  }
231 
250  public function beginAuthentication( array $reqs, $returnToUrl ) {
251  $session = $this->request->getSession();
252  if ( !$session->canSetUser() ) {
253  // Caller should have called canAuthenticateNow()
254  $session->remove( 'AuthManager::authnState' );
255  throw new \LogicException( 'Authentication is not possible now' );
256  }
257 
258  $guessUserName = null;
259  foreach ( $reqs as $req ) {
260  $req->returnToUrl = $returnToUrl;
261  // @codeCoverageIgnoreStart
262  if ( $req->username !== null && $req->username !== '' ) {
263  if ( $guessUserName === null ) {
264  $guessUserName = $req->username;
265  } elseif ( $guessUserName !== $req->username ) {
266  $guessUserName = null;
267  break;
268  }
269  }
270  // @codeCoverageIgnoreEnd
271  }
272 
273  // Check for special-case login of a just-created account
276  );
277  if ( $req ) {
278  if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
279  throw new \LogicException(
280  'CreatedAccountAuthenticationRequests are only valid on ' .
281  'the same AuthManager that created the account'
282  );
283  }
284 
285  $user = User::newFromName( $req->username );
286  // @codeCoverageIgnoreStart
287  if ( !$user ) {
288  throw new \UnexpectedValueException(
289  "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
290  );
291  } elseif ( $user->getId() != $req->id ) {
292  throw new \UnexpectedValueException(
293  "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
294  );
295  }
296  // @codeCoverageIgnoreEnd
297 
298  $this->logger->info( 'Logging in {user} after account creation', [
299  'user' => $user->getName(),
300  ] );
302  $this->setSessionDataForUser( $user );
303  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
304  $session->remove( 'AuthManager::authnState' );
305  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
306  return $ret;
307  }
308 
309  $this->removeAuthenticationSessionData( null );
310 
311  foreach ( $this->getPreAuthenticationProviders() as $provider ) {
312  $status = $provider->testForAuthentication( $reqs );
313  if ( !$status->isGood() ) {
314  $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
316  Status::wrap( $status )->getMessage()
317  );
318  $this->callMethodOnProviders( 7, 'postAuthentication',
319  [ User::newFromName( $guessUserName ) ?: null, $ret ]
320  );
321  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, null, $guessUserName ] );
322  return $ret;
323  }
324  }
325 
326  $state = [
327  'reqs' => $reqs,
328  'returnToUrl' => $returnToUrl,
329  'guessUserName' => $guessUserName,
330  'primary' => null,
331  'primaryResponse' => null,
332  'secondary' => [],
333  'maybeLink' => [],
334  'continueRequests' => [],
335  ];
336 
337  // Preserve state from a previous failed login
340  );
341  if ( $req ) {
342  $state['maybeLink'] = $req->maybeLink;
343  }
344 
345  $session = $this->request->getSession();
346  $session->setSecret( 'AuthManager::authnState', $state );
347  $session->persist();
348 
349  return $this->continueAuthentication( $reqs );
350  }
351 
374  public function continueAuthentication( array $reqs ) {
375  $session = $this->request->getSession();
376  try {
377  if ( !$session->canSetUser() ) {
378  // Caller should have called canAuthenticateNow()
379  // @codeCoverageIgnoreStart
380  throw new \LogicException( 'Authentication is not possible now' );
381  // @codeCoverageIgnoreEnd
382  }
383 
384  $state = $session->getSecret( 'AuthManager::authnState' );
385  if ( !is_array( $state ) ) {
387  wfMessage( 'authmanager-authn-not-in-progress' )
388  );
389  }
390  $state['continueRequests'] = [];
391 
392  $guessUserName = $state['guessUserName'];
393 
394  foreach ( $reqs as $req ) {
395  $req->returnToUrl = $state['returnToUrl'];
396  }
397 
398  // Step 1: Choose an primary authentication provider, and call it until it succeeds.
399 
400  if ( $state['primary'] === null ) {
401  // We haven't picked a PrimaryAuthenticationProvider yet
402  // @codeCoverageIgnoreStart
403  $guessUserName = null;
404  foreach ( $reqs as $req ) {
405  if ( $req->username !== null && $req->username !== '' ) {
406  if ( $guessUserName === null ) {
407  $guessUserName = $req->username;
408  } elseif ( $guessUserName !== $req->username ) {
409  $guessUserName = null;
410  break;
411  }
412  }
413  }
414  $state['guessUserName'] = $guessUserName;
415  // @codeCoverageIgnoreEnd
416  $state['reqs'] = $reqs;
417 
418  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
419  $res = $provider->beginPrimaryAuthentication( $reqs );
420  switch ( $res->status ) {
422  $state['primary'] = $id;
423  $state['primaryResponse'] = $res;
424  $this->logger->debug( "Primary login with $id succeeded" );
425  break 2;
427  $this->logger->debug( "Login failed in primary authentication by $id" );
428  if ( $res->createRequest || $state['maybeLink'] ) {
429  $res->createRequest = new CreateFromLoginAuthenticationRequest(
430  $res->createRequest, $state['maybeLink']
431  );
432  }
433  $this->callMethodOnProviders( 7, 'postAuthentication',
434  [ User::newFromName( $guessUserName ) ?: null, $res ]
435  );
436  $session->remove( 'AuthManager::authnState' );
437  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
438  return $res;
440  // Continue loop
441  break;
444  $this->logger->debug( "Primary login with $id returned $res->status" );
445  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
446  $state['primary'] = $id;
447  $state['continueRequests'] = $res->neededRequests;
448  $session->setSecret( 'AuthManager::authnState', $state );
449  return $res;
450 
451  // @codeCoverageIgnoreStart
452  default:
453  throw new \DomainException(
454  get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
455  );
456  // @codeCoverageIgnoreEnd
457  }
458  }
459  if ( $state['primary'] === null ) {
460  $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
462  wfMessage( 'authmanager-authn-no-primary' )
463  );
464  $this->callMethodOnProviders( 7, 'postAuthentication',
465  [ User::newFromName( $guessUserName ) ?: null, $ret ]
466  );
467  $session->remove( 'AuthManager::authnState' );
468  return $ret;
469  }
470  } elseif ( $state['primaryResponse'] === null ) {
471  $provider = $this->getAuthenticationProvider( $state['primary'] );
472  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
473  // Configuration changed? Force them to start over.
474  // @codeCoverageIgnoreStart
476  wfMessage( 'authmanager-authn-not-in-progress' )
477  );
478  $this->callMethodOnProviders( 7, 'postAuthentication',
479  [ User::newFromName( $guessUserName ) ?: null, $ret ]
480  );
481  $session->remove( 'AuthManager::authnState' );
482  return $ret;
483  // @codeCoverageIgnoreEnd
484  }
485  $id = $provider->getUniqueId();
486  $res = $provider->continuePrimaryAuthentication( $reqs );
487  switch ( $res->status ) {
489  $state['primaryResponse'] = $res;
490  $this->logger->debug( "Primary login with $id succeeded" );
491  break;
493  $this->logger->debug( "Login failed in primary authentication by $id" );
494  if ( $res->createRequest || $state['maybeLink'] ) {
495  $res->createRequest = new CreateFromLoginAuthenticationRequest(
496  $res->createRequest, $state['maybeLink']
497  );
498  }
499  $this->callMethodOnProviders( 7, 'postAuthentication',
500  [ User::newFromName( $guessUserName ) ?: null, $res ]
501  );
502  $session->remove( 'AuthManager::authnState' );
503  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
504  return $res;
507  $this->logger->debug( "Primary login with $id returned $res->status" );
508  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
509  $state['continueRequests'] = $res->neededRequests;
510  $session->setSecret( 'AuthManager::authnState', $state );
511  return $res;
512  default:
513  throw new \DomainException(
514  get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
515  );
516  }
517  }
518 
519  $res = $state['primaryResponse'];
520  if ( $res->username === null ) {
521  $provider = $this->getAuthenticationProvider( $state['primary'] );
522  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
523  // Configuration changed? Force them to start over.
524  // @codeCoverageIgnoreStart
526  wfMessage( 'authmanager-authn-not-in-progress' )
527  );
528  $this->callMethodOnProviders( 7, 'postAuthentication',
529  [ User::newFromName( $guessUserName ) ?: null, $ret ]
530  );
531  $session->remove( 'AuthManager::authnState' );
532  return $ret;
533  // @codeCoverageIgnoreEnd
534  }
535 
536  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
537  $res->linkRequest &&
538  // don't confuse the user with an incorrect message if linking is disabled
540  ) {
541  $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
542  $msg = 'authmanager-authn-no-local-user-link';
543  } else {
544  $msg = 'authmanager-authn-no-local-user';
545  }
546  $this->logger->debug(
547  "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
548  );
550  $ret->neededRequests = $this->getAuthenticationRequestsInternal(
551  self::ACTION_LOGIN,
552  [],
554  );
555  if ( $res->createRequest || $state['maybeLink'] ) {
556  $ret->createRequest = new CreateFromLoginAuthenticationRequest(
557  $res->createRequest, $state['maybeLink']
558  );
559  $ret->neededRequests[] = $ret->createRequest;
560  }
561  $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
562  $session->setSecret( 'AuthManager::authnState', [
563  'reqs' => [], // Will be filled in later
564  'primary' => null,
565  'primaryResponse' => null,
566  'secondary' => [],
567  'continueRequests' => $ret->neededRequests,
568  ] + $state );
569  return $ret;
570  }
571 
572  // Step 2: Primary authentication succeeded, create the User object
573  // (and add the user locally if necessary)
574 
575  $user = User::newFromName( $res->username, 'usable' );
576  if ( !$user ) {
577  throw new \DomainException(
578  get_class( $provider ) . " returned an invalid username: {$res->username}"
579  );
580  }
581  if ( $user->getId() === 0 ) {
582  // User doesn't exist locally. Create it.
583  $this->logger->info( 'Auto-creating {user} on login', [
584  'user' => $user->getName(),
585  ] );
586  $status = $this->autoCreateUser( $user, $state['primary'], false );
587  if ( !$status->isGood() ) {
589  Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
590  );
591  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
592  $session->remove( 'AuthManager::authnState' );
593  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
594  return $ret;
595  }
596  }
597 
598  // Step 3: Iterate over all the secondary authentication providers.
599 
600  $beginReqs = $state['reqs'];
601 
602  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
603  if ( !isset( $state['secondary'][$id] ) ) {
604  // This provider isn't started yet, so we pass it the set
605  // of reqs from beginAuthentication instead of whatever
606  // might have been used by a previous provider in line.
607  $func = 'beginSecondaryAuthentication';
608  $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
609  } elseif ( !$state['secondary'][$id] ) {
610  $func = 'continueSecondaryAuthentication';
611  $res = $provider->continueSecondaryAuthentication( $user, $reqs );
612  } else {
613  continue;
614  }
615  switch ( $res->status ) {
617  $this->logger->debug( "Secondary login with $id succeeded" );
618  // fall through
620  $state['secondary'][$id] = true;
621  break;
623  $this->logger->debug( "Login failed in secondary authentication by $id" );
624  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
625  $session->remove( 'AuthManager::authnState' );
626  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, $user, $user->getName() ] );
627  return $res;
630  $this->logger->debug( "Secondary login with $id returned " . $res->status );
631  $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
632  $state['secondary'][$id] = false;
633  $state['continueRequests'] = $res->neededRequests;
634  $session->setSecret( 'AuthManager::authnState', $state );
635  return $res;
636 
637  // @codeCoverageIgnoreStart
638  default:
639  throw new \DomainException(
640  get_class( $provider ) . "::{$func}() returned $res->status"
641  );
642  // @codeCoverageIgnoreEnd
643  }
644  }
645 
646  // Step 4: Authentication complete! Set the user in the session and
647  // clean up.
648 
649  $this->logger->info( 'Login for {user} succeeded', [
650  'user' => $user->getName(),
651  ] );
654  );
655  $this->setSessionDataForUser( $user, $req && $req->rememberMe );
657  $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
658  $session->remove( 'AuthManager::authnState' );
659  $this->removeAuthenticationSessionData( null );
660  \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
661  return $ret;
662  } catch ( \Exception $ex ) {
663  $session->remove( 'AuthManager::authnState' );
664  throw $ex;
665  }
666  }
667 
679  public function securitySensitiveOperationStatus( $operation ) {
680  $status = self::SEC_OK;
681 
682  $this->logger->debug( __METHOD__ . ": Checking $operation" );
683 
684  $session = $this->request->getSession();
685  $aId = $session->getUser()->getId();
686  if ( $aId === 0 ) {
687  // User isn't authenticated. DWIM?
688  $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
689  $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
690  return $status;
691  }
692 
693  if ( $session->canSetUser() ) {
694  $id = $session->get( 'AuthManager:lastAuthId' );
695  $last = $session->get( 'AuthManager:lastAuthTimestamp' );
696  if ( $id !== $aId || $last === null ) {
697  $timeSinceLogin = PHP_INT_MAX; // Forever ago
698  } else {
699  $timeSinceLogin = max( 0, time() - $last );
700  }
701 
702  $thresholds = $this->config->get( 'ReauthenticateTime' );
703  if ( isset( $thresholds[$operation] ) ) {
704  $threshold = $thresholds[$operation];
705  } elseif ( isset( $thresholds['default'] ) ) {
706  $threshold = $thresholds['default'];
707  } else {
708  throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
709  }
710 
711  if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
712  $status = self::SEC_REAUTH;
713  }
714  } else {
715  $timeSinceLogin = -1;
716 
717  $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
718  if ( isset( $pass[$operation] ) ) {
719  $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
720  } elseif ( isset( $pass['default'] ) ) {
721  $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
722  } else {
723  throw new \UnexpectedValueException(
724  '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
725  );
726  }
727  }
728 
729  \Hooks::run( 'SecuritySensitiveOperationStatus', [
730  &$status, $operation, $session, $timeSinceLogin
731  ] );
732 
733  // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
734  if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
735  $status = self::SEC_FAIL;
736  }
737 
738  $this->logger->info( __METHOD__ . ": $operation is $status" );
739 
740  return $status;
741  }
742 
749  public function userCanAuthenticate( $username ) {
750  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
751  if ( $provider->testUserCanAuthenticate( $username ) ) {
752  return true;
753  }
754  }
755  return false;
756  }
757 
772  public function normalizeUsername( $username ) {
773  $ret = [];
774  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
775  $normalized = $provider->providerNormalizeUsername( $username );
776  if ( $normalized !== null ) {
777  $ret[$normalized] = true;
778  }
779  }
780  return array_keys( $ret );
781  }
782 
797  public function revokeAccessForUser( $username ) {
798  $this->logger->info( 'Revoking access for {user}', [
799  'user' => $username,
800  ] );
801  $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
802  }
803 
813  public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
814  $any = false;
815  $providers = $this->getPrimaryAuthenticationProviders() +
817  foreach ( $providers as $provider ) {
818  $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
819  if ( !$status->isGood() ) {
820  return Status::wrap( $status );
821  }
822  $any = $any || $status->value !== 'ignored';
823  }
824  if ( !$any ) {
825  $status = Status::newGood( 'ignored' );
826  $status->warning( 'authmanager-change-not-supported' );
827  return $status;
828  }
829  return Status::newGood();
830  }
831 
844  $this->logger->info( 'Changing authentication data for {user} class {what}', [
845  'user' => is_string( $req->username ) ? $req->username : '<no name>',
846  'what' => get_class( $req ),
847  ] );
848 
849  $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
850 
851  // When the main account's authentication data is changed, invalidate
852  // all BotPasswords too.
854  }
855 
867  public function canCreateAccounts() {
868  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
869  switch ( $provider->accountCreationType() ) {
872  return true;
873  }
874  }
875  return false;
876  }
877 
886  public function canCreateAccount( $username, $options = [] ) {
887  // Back compat
888  if ( is_int( $options ) ) {
889  $options = [ 'flags' => $options ];
890  }
891  $options += [
892  'flags' => User::READ_NORMAL,
893  'creating' => false,
894  ];
895  $flags = $options['flags'];
896 
897  if ( !$this->canCreateAccounts() ) {
898  return Status::newFatal( 'authmanager-create-disabled' );
899  }
900 
901  if ( $this->userExists( $username, $flags ) ) {
902  return Status::newFatal( 'userexists' );
903  }
904 
905  $user = User::newFromName( $username, 'creatable' );
906  if ( !is_object( $user ) ) {
907  return Status::newFatal( 'noname' );
908  } else {
909  $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
910  if ( $user->getId() !== 0 ) {
911  return Status::newFatal( 'userexists' );
912  }
913  }
914 
915  // Denied by providers?
916  $providers = $this->getPreAuthenticationProviders() +
919  foreach ( $providers as $provider ) {
920  $status = $provider->testUserForCreation( $user, false, $options );
921  if ( !$status->isGood() ) {
922  return Status::wrap( $status );
923  }
924  }
925 
926  return Status::newGood();
927  }
928 
934  public function checkAccountCreatePermissions( User $creator ) {
935  // Wiki is read-only?
936  if ( wfReadOnly() ) {
937  return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
938  }
939 
940  // This is awful, this permission check really shouldn't go through Title.
941  $permErrors = \SpecialPage::getTitleFor( 'CreateAccount' )
942  ->getUserPermissionsErrors( 'createaccount', $creator, 'secure' );
943  if ( $permErrors ) {
945  foreach ( $permErrors as $args ) {
946  call_user_func_array( [ $status, 'fatal' ], $args );
947  }
948  return $status;
949  }
950 
951  $block = $creator->isBlockedFromCreateAccount();
952  if ( $block ) {
953  $errorParams = [
954  $block->getTarget(),
955  $block->mReason ?: wfMessage( 'blockednoreason' )->text(),
956  $block->getByName()
957  ];
958 
959  if ( $block->getType() === \Block::TYPE_RANGE ) {
960  $errorMessage = 'cantcreateaccount-range-text';
961  $errorParams[] = $this->getRequest()->getIP();
962  } else {
963  $errorMessage = 'cantcreateaccount-text';
964  }
965 
966  return Status::newFatal( wfMessage( $errorMessage, $errorParams ) );
967  }
968 
969  $ip = $this->getRequest()->getIP();
970  if ( $creator->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
971  return Status::newFatal( 'sorbs_create_account_reason' );
972  }
973 
974  return Status::newGood();
975  }
976 
996  public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
997  $session = $this->request->getSession();
998  if ( !$this->canCreateAccounts() ) {
999  // Caller should have called canCreateAccounts()
1000  $session->remove( 'AuthManager::accountCreationState' );
1001  throw new \LogicException( 'Account creation is not possible' );
1002  }
1003 
1004  try {
1006  } catch ( \UnexpectedValueException $ex ) {
1007  $username = null;
1008  }
1009  if ( $username === null ) {
1010  $this->logger->debug( __METHOD__ . ': No username provided' );
1011  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1012  }
1013 
1014  // Permissions check
1015  $status = $this->checkAccountCreatePermissions( $creator );
1016  if ( !$status->isGood() ) {
1017  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1018  'user' => $username,
1019  'creator' => $creator->getName(),
1020  'reason' => $status->getWikiText( null, null, 'en' )
1021  ] );
1022  return AuthenticationResponse::newFail( $status->getMessage() );
1023  }
1024 
1025  $status = $this->canCreateAccount(
1026  $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1027  );
1028  if ( !$status->isGood() ) {
1029  $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1030  'user' => $username,
1031  'creator' => $creator->getName(),
1032  'reason' => $status->getWikiText( null, null, 'en' )
1033  ] );
1034  return AuthenticationResponse::newFail( $status->getMessage() );
1035  }
1036 
1037  $user = User::newFromName( $username, 'creatable' );
1038  foreach ( $reqs as $req ) {
1039  $req->username = $username;
1040  $req->returnToUrl = $returnToUrl;
1041  if ( $req instanceof UserDataAuthenticationRequest ) {
1042  $status = $req->populateUser( $user );
1043  if ( !$status->isGood() ) {
1045  $session->remove( 'AuthManager::accountCreationState' );
1046  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1047  'user' => $user->getName(),
1048  'creator' => $creator->getName(),
1049  'reason' => $status->getWikiText( null, null, 'en' ),
1050  ] );
1051  return AuthenticationResponse::newFail( $status->getMessage() );
1052  }
1053  }
1054  }
1055 
1056  $this->removeAuthenticationSessionData( null );
1057 
1058  $state = [
1059  'username' => $username,
1060  'userid' => 0,
1061  'creatorid' => $creator->getId(),
1062  'creatorname' => $creator->getName(),
1063  'reqs' => $reqs,
1064  'returnToUrl' => $returnToUrl,
1065  'primary' => null,
1066  'primaryResponse' => null,
1067  'secondary' => [],
1068  'continueRequests' => [],
1069  'maybeLink' => [],
1070  'ranPreTests' => false,
1071  ];
1072 
1073  // Special case: converting a login to an account creation
1076  );
1077  if ( $req ) {
1078  $state['maybeLink'] = $req->maybeLink;
1079 
1080  if ( $req->createRequest ) {
1081  $reqs[] = $req->createRequest;
1082  $state['reqs'][] = $req->createRequest;
1083  }
1084  }
1085 
1086  $session->setSecret( 'AuthManager::accountCreationState', $state );
1087  $session->persist();
1088 
1089  return $this->continueAccountCreation( $reqs );
1090  }
1091 
1097  public function continueAccountCreation( array $reqs ) {
1098  $session = $this->request->getSession();
1099  try {
1100  if ( !$this->canCreateAccounts() ) {
1101  // Caller should have called canCreateAccounts()
1102  $session->remove( 'AuthManager::accountCreationState' );
1103  throw new \LogicException( 'Account creation is not possible' );
1104  }
1105 
1106  $state = $session->getSecret( 'AuthManager::accountCreationState' );
1107  if ( !is_array( $state ) ) {
1109  wfMessage( 'authmanager-create-not-in-progress' )
1110  );
1111  }
1112  $state['continueRequests'] = [];
1113 
1114  // Step 0: Prepare and validate the input
1115 
1116  $user = User::newFromName( $state['username'], 'creatable' );
1117  if ( !is_object( $user ) ) {
1118  $session->remove( 'AuthManager::accountCreationState' );
1119  $this->logger->debug( __METHOD__ . ': Invalid username', [
1120  'user' => $state['username'],
1121  ] );
1122  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1123  }
1124 
1125  if ( $state['creatorid'] ) {
1126  $creator = User::newFromId( $state['creatorid'] );
1127  } else {
1128  $creator = new User;
1129  $creator->setName( $state['creatorname'] );
1130  }
1131 
1132  // Avoid account creation races on double submissions
1134  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1135  if ( !$lock ) {
1136  // Don't clear AuthManager::accountCreationState for this code
1137  // path because the process that won the race owns it.
1138  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1139  'user' => $user->getName(),
1140  'creator' => $creator->getName(),
1141  ] );
1142  return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1143  }
1144 
1145  // Permissions check
1146  $status = $this->checkAccountCreatePermissions( $creator );
1147  if ( !$status->isGood() ) {
1148  $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1149  'user' => $user->getName(),
1150  'creator' => $creator->getName(),
1151  'reason' => $status->getWikiText( null, null, 'en' )
1152  ] );
1153  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1154  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1155  $session->remove( 'AuthManager::accountCreationState' );
1156  return $ret;
1157  }
1158 
1159  // Load from master for existence check
1160  $user->load( User::READ_LOCKING );
1161 
1162  if ( $state['userid'] === 0 ) {
1163  if ( $user->getId() != 0 ) {
1164  $this->logger->debug( __METHOD__ . ': User exists locally', [
1165  'user' => $user->getName(),
1166  'creator' => $creator->getName(),
1167  ] );
1168  $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1169  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1170  $session->remove( 'AuthManager::accountCreationState' );
1171  return $ret;
1172  }
1173  } else {
1174  if ( $user->getId() == 0 ) {
1175  $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1176  'user' => $user->getName(),
1177  'creator' => $creator->getName(),
1178  'expected_id' => $state['userid'],
1179  ] );
1180  throw new \UnexpectedValueException(
1181  "User \"{$state['username']}\" should exist now, but doesn't!"
1182  );
1183  }
1184  if ( $user->getId() != $state['userid'] ) {
1185  $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1186  'user' => $user->getName(),
1187  'creator' => $creator->getName(),
1188  'expected_id' => $state['userid'],
1189  'actual_id' => $user->getId(),
1190  ] );
1191  throw new \UnexpectedValueException(
1192  "User \"{$state['username']}\" exists, but " .
1193  "ID {$user->getId()} != {$state['userid']}!"
1194  );
1195  }
1196  }
1197  foreach ( $state['reqs'] as $req ) {
1198  if ( $req instanceof UserDataAuthenticationRequest ) {
1199  $status = $req->populateUser( $user );
1200  if ( !$status->isGood() ) {
1201  // This should never happen...
1203  $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1204  'user' => $user->getName(),
1205  'creator' => $creator->getName(),
1206  'reason' => $status->getWikiText( null, null, 'en' ),
1207  ] );
1208  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1209  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1210  $session->remove( 'AuthManager::accountCreationState' );
1211  return $ret;
1212  }
1213  }
1214  }
1215 
1216  foreach ( $reqs as $req ) {
1217  $req->returnToUrl = $state['returnToUrl'];
1218  $req->username = $state['username'];
1219  }
1220 
1221  // Run pre-creation tests, if we haven't already
1222  if ( !$state['ranPreTests'] ) {
1223  $providers = $this->getPreAuthenticationProviders() +
1226  foreach ( $providers as $id => $provider ) {
1227  $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1228  if ( !$status->isGood() ) {
1229  $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1230  'user' => $user->getName(),
1231  'creator' => $creator->getName(),
1232  ] );
1234  Status::wrap( $status )->getMessage()
1235  );
1236  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1237  $session->remove( 'AuthManager::accountCreationState' );
1238  return $ret;
1239  }
1240  }
1241 
1242  $state['ranPreTests'] = true;
1243  }
1244 
1245  // Step 1: Choose a primary authentication provider and call it until it succeeds.
1246 
1247  if ( $state['primary'] === null ) {
1248  // We haven't picked a PrimaryAuthenticationProvider yet
1249  foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1250  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1251  continue;
1252  }
1253  $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1254  switch ( $res->status ) {
1256  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1257  'user' => $user->getName(),
1258  'creator' => $creator->getName(),
1259  ] );
1260  $state['primary'] = $id;
1261  $state['primaryResponse'] = $res;
1262  break 2;
1264  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1265  'user' => $user->getName(),
1266  'creator' => $creator->getName(),
1267  ] );
1268  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1269  $session->remove( 'AuthManager::accountCreationState' );
1270  return $res;
1272  // Continue loop
1273  break;
1276  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1277  'user' => $user->getName(),
1278  'creator' => $creator->getName(),
1279  ] );
1280  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1281  $state['primary'] = $id;
1282  $state['continueRequests'] = $res->neededRequests;
1283  $session->setSecret( 'AuthManager::accountCreationState', $state );
1284  return $res;
1285 
1286  // @codeCoverageIgnoreStart
1287  default:
1288  throw new \DomainException(
1289  get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1290  );
1291  // @codeCoverageIgnoreEnd
1292  }
1293  }
1294  if ( $state['primary'] === null ) {
1295  $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1296  'user' => $user->getName(),
1297  'creator' => $creator->getName(),
1298  ] );
1300  wfMessage( 'authmanager-create-no-primary' )
1301  );
1302  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1303  $session->remove( 'AuthManager::accountCreationState' );
1304  return $ret;
1305  }
1306  } elseif ( $state['primaryResponse'] === null ) {
1307  $provider = $this->getAuthenticationProvider( $state['primary'] );
1308  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1309  // Configuration changed? Force them to start over.
1310  // @codeCoverageIgnoreStart
1312  wfMessage( 'authmanager-create-not-in-progress' )
1313  );
1314  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1315  $session->remove( 'AuthManager::accountCreationState' );
1316  return $ret;
1317  // @codeCoverageIgnoreEnd
1318  }
1319  $id = $provider->getUniqueId();
1320  $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1321  switch ( $res->status ) {
1323  $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1324  'user' => $user->getName(),
1325  'creator' => $creator->getName(),
1326  ] );
1327  $state['primaryResponse'] = $res;
1328  break;
1330  $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1331  'user' => $user->getName(),
1332  'creator' => $creator->getName(),
1333  ] );
1334  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1335  $session->remove( 'AuthManager::accountCreationState' );
1336  return $res;
1339  $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1340  'user' => $user->getName(),
1341  'creator' => $creator->getName(),
1342  ] );
1343  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1344  $state['continueRequests'] = $res->neededRequests;
1345  $session->setSecret( 'AuthManager::accountCreationState', $state );
1346  return $res;
1347  default:
1348  throw new \DomainException(
1349  get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1350  );
1351  }
1352  }
1353 
1354  // Step 2: Primary authentication succeeded, create the User object
1355  // and add the user locally.
1356 
1357  if ( $state['userid'] === 0 ) {
1358  $this->logger->info( 'Creating user {user} during account creation', [
1359  'user' => $user->getName(),
1360  'creator' => $creator->getName(),
1361  ] );
1362  $status = $user->addToDatabase();
1363  if ( !$status->isOk() ) {
1364  // @codeCoverageIgnoreStart
1365  $ret = AuthenticationResponse::newFail( $status->getMessage() );
1366  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1367  $session->remove( 'AuthManager::accountCreationState' );
1368  return $ret;
1369  // @codeCoverageIgnoreEnd
1370  }
1371  $this->setDefaultUserOptions( $user, $creator->isAnon() );
1372  \Hooks::run( 'LocalUserCreated', [ $user, false ] );
1373  $user->saveSettings();
1374  $state['userid'] = $user->getId();
1375 
1376  // Update user count
1377  \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1378 
1379  // Watch user's userpage and talk page
1380  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1381 
1382  // Inform the provider
1383  $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1384 
1385  // Log the creation
1386  if ( $this->config->get( 'NewUserLog' ) ) {
1387  $isAnon = $creator->isAnon();
1388  $logEntry = new \ManualLogEntry(
1389  'newusers',
1390  $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1391  );
1392  $logEntry->setPerformer( $isAnon ? $user : $creator );
1393  $logEntry->setTarget( $user->getUserPage() );
1396  );
1397  $logEntry->setComment( $req ? $req->reason : '' );
1398  $logEntry->setParameters( [
1399  '4::userid' => $user->getId(),
1400  ] );
1401  $logid = $logEntry->insert();
1402  $logEntry->publish( $logid );
1403  }
1404  }
1405 
1406  // Step 3: Iterate over all the secondary authentication providers.
1407 
1408  $beginReqs = $state['reqs'];
1409 
1410  foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1411  if ( !isset( $state['secondary'][$id] ) ) {
1412  // This provider isn't started yet, so we pass it the set
1413  // of reqs from beginAuthentication instead of whatever
1414  // might have been used by a previous provider in line.
1415  $func = 'beginSecondaryAccountCreation';
1416  $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1417  } elseif ( !$state['secondary'][$id] ) {
1418  $func = 'continueSecondaryAccountCreation';
1419  $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1420  } else {
1421  continue;
1422  }
1423  switch ( $res->status ) {
1425  $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1426  'user' => $user->getName(),
1427  'creator' => $creator->getName(),
1428  ] );
1429  // fall through
1431  $state['secondary'][$id] = true;
1432  break;
1435  $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1436  'user' => $user->getName(),
1437  'creator' => $creator->getName(),
1438  ] );
1439  $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1440  $state['secondary'][$id] = false;
1441  $state['continueRequests'] = $res->neededRequests;
1442  $session->setSecret( 'AuthManager::accountCreationState', $state );
1443  return $res;
1445  throw new \DomainException(
1446  get_class( $provider ) . "::{$func}() returned $res->status." .
1447  ' Secondary providers are not allowed to fail account creation, that' .
1448  ' should have been done via testForAccountCreation().'
1449  );
1450  // @codeCoverageIgnoreStart
1451  default:
1452  throw new \DomainException(
1453  get_class( $provider ) . "::{$func}() returned $res->status"
1454  );
1455  // @codeCoverageIgnoreEnd
1456  }
1457  }
1458 
1459  $id = $user->getId();
1460  $name = $user->getName();
1461  $req = new CreatedAccountAuthenticationRequest( $id, $name );
1463  $ret->loginRequest = $req;
1464  $this->createdAccountAuthenticationRequests[] = $req;
1465 
1466  $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1467  'user' => $user->getName(),
1468  'creator' => $creator->getName(),
1469  ] );
1470 
1471  $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1472  $session->remove( 'AuthManager::accountCreationState' );
1473  $this->removeAuthenticationSessionData( null );
1474  return $ret;
1475  } catch ( \Exception $ex ) {
1476  $session->remove( 'AuthManager::accountCreationState' );
1477  throw $ex;
1478  }
1479  }
1480 
1489  public function autoCreateUser( User $user, $source, $login = true ) {
1490  if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1492  ) {
1493  throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1494  }
1495 
1496  $username = $user->getName();
1497 
1498  // Try the local user from the slave DB
1499  $localId = User::idFromName( $username );
1501 
1502  // Fetch the user ID from the master, so that we don't try to create the user
1503  // when they already exist, due to replication lag
1504  // @codeCoverageIgnoreStart
1505  if ( !$localId && wfGetLB()->getReaderIndex() != 0 ) {
1508  }
1509  // @codeCoverageIgnoreEnd
1510 
1511  if ( $localId ) {
1512  $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1513  'username' => $username,
1514  ] );
1515  $user->setId( $localId );
1516  $user->loadFromId( $flags );
1517  if ( $login ) {
1518  $this->setSessionDataForUser( $user );
1519  }
1521  $status->warning( 'userexists' );
1522  return $status;
1523  }
1524 
1525  // Wiki is read-only?
1526  if ( wfReadOnly() ) {
1527  $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
1528  'username' => $username,
1529  'reason' => wfReadOnlyReason(),
1530  ] );
1531  $user->setId( 0 );
1532  $user->loadFromId();
1533  return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
1534  }
1535 
1536  // Check the session, if we tried to create this user already there's
1537  // no point in retrying.
1538  $session = $this->request->getSession();
1539  if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1540  $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1541  'username' => $username,
1542  'sessionid' => $session->getId(),
1543  ] );
1544  $user->setId( 0 );
1545  $user->loadFromId();
1546  $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1547  if ( $reason instanceof StatusValue ) {
1548  return Status::wrap( $reason );
1549  } else {
1550  return Status::newFatal( $reason );
1551  }
1552  }
1553 
1554  // Is the username creatable?
1555  if ( !User::isCreatableName( $username ) ) {
1556  $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1557  'username' => $username,
1558  ] );
1559  $session->set( 'AuthManager::AutoCreateBlacklist', 'noname', 600 );
1560  $user->setId( 0 );
1561  $user->loadFromId();
1562  return Status::newFatal( 'noname' );
1563  }
1564 
1565  // Is the IP user able to create accounts?
1566  $anon = new User;
1567  if ( !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' ) ) {
1568  $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1569  'username' => $username,
1570  'ip' => $anon->getName(),
1571  ] );
1572  $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm', 600 );
1573  $session->persist();
1574  $user->setId( 0 );
1575  $user->loadFromId();
1576  return Status::newFatal( 'authmanager-autocreate-noperm' );
1577  }
1578 
1579  // Avoid account creation races on double submissions
1581  $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1582  if ( !$lock ) {
1583  $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1584  'user' => $username,
1585  ] );
1586  $user->setId( 0 );
1587  $user->loadFromId();
1588  return Status::newFatal( 'usernameinprogress' );
1589  }
1590 
1591  // Denied by providers?
1592  $options = [
1593  'flags' => User::READ_LATEST,
1594  'creating' => true,
1595  ];
1596  $providers = $this->getPreAuthenticationProviders() +
1599  foreach ( $providers as $provider ) {
1600  $status = $provider->testUserForCreation( $user, $source, $options );
1601  if ( !$status->isGood() ) {
1602  $ret = Status::wrap( $status );
1603  $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1604  'username' => $username,
1605  'reason' => $ret->getWikiText( null, null, 'en' ),
1606  ] );
1607  $session->set( 'AuthManager::AutoCreateBlacklist', $status, 600 );
1608  $user->setId( 0 );
1609  $user->loadFromId();
1610  return $ret;
1611  }
1612  }
1613 
1614  // Ignore warnings about master connections/writes...hard to avoid here
1615  \Profiler::instance()->getTransactionProfiler()->resetExpectations();
1616 
1617  $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1618  if ( $cache->get( $backoffKey ) ) {
1619  $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1620  'username' => $username,
1621  ] );
1622  $user->setId( 0 );
1623  $user->loadFromId();
1624  return Status::newFatal( 'authmanager-autocreate-exception' );
1625  }
1626 
1627  // Checks passed, create the user...
1628  $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
1629  $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1630  'username' => $username,
1631  'from' => $from,
1632  ] );
1633 
1634  try {
1635  $status = $user->addToDatabase();
1636  if ( !$status->isOk() ) {
1637  // double-check for a race condition (T70012)
1639  if ( $localId ) {
1640  $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1641  'username' => $username,
1642  ] );
1643  $user->setId( $localId );
1644  $user->loadFromId( User::READ_LATEST );
1645  if ( $login ) {
1646  $this->setSessionDataForUser( $user );
1647  }
1649  $status->warning( 'userexists' );
1650  } else {
1651  $this->logger->error( __METHOD__ . ': {username} failed with message {message}', [
1652  'username' => $username,
1653  'message' => $status->getWikiText( null, null, 'en' )
1654  ] );
1655  $user->setId( 0 );
1656  $user->loadFromId();
1657  }
1658  return $status;
1659  }
1660  } catch ( \Exception $ex ) {
1661  $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1662  'username' => $username,
1663  'exception' => $ex,
1664  ] );
1665  // Do not keep throwing errors for a while
1666  $cache->set( $backoffKey, 1, 600 );
1667  // Bubble up error; which should normally trigger DB rollbacks
1668  throw $ex;
1669  }
1670 
1671  $this->setDefaultUserOptions( $user, false );
1672 
1673  // Inform the providers
1674  $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1675 
1676  \Hooks::run( 'AuthPluginAutoCreate', [ $user ], '1.27' );
1677  \Hooks::run( 'LocalUserCreated', [ $user, true ] );
1678  $user->saveSettings();
1679 
1680  // Update user count
1681  \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1682 
1683  // Watch user's userpage and talk page
1684  $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1685 
1686  // Log the creation
1687  if ( $this->config->get( 'NewUserLog' ) ) {
1688  $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1689  $logEntry->setPerformer( $user );
1690  $logEntry->setTarget( $user->getUserPage() );
1691  $logEntry->setComment( '' );
1692  $logEntry->setParameters( [
1693  '4::userid' => $user->getId(),
1694  ] );
1695  $logid = $logEntry->insert();
1696  }
1697 
1698  // Commit database changes, so even if something else later blows up
1699  // the newly-created user doesn't get lost.
1700  wfGetLBFactory()->commitMasterChanges( __METHOD__ );
1701 
1702  if ( $login ) {
1703  $this->setSessionDataForUser( $user );
1704  }
1705 
1706  return Status::newGood();
1707  }
1708 
1720  public function canLinkAccounts() {
1721  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1722  if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1723  return true;
1724  }
1725  }
1726  return false;
1727  }
1728 
1738  public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1739  $session = $this->request->getSession();
1740  $session->remove( 'AuthManager::accountLinkState' );
1741 
1742  if ( !$this->canLinkAccounts() ) {
1743  // Caller should have called canLinkAccounts()
1744  throw new \LogicException( 'Account linking is not possible' );
1745  }
1746 
1747  if ( $user->getId() === 0 ) {
1748  if ( !User::isUsableName( $user->getName() ) ) {
1749  $msg = wfMessage( 'noname' );
1750  } else {
1751  $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1752  }
1753  return AuthenticationResponse::newFail( $msg );
1754  }
1755  foreach ( $reqs as $req ) {
1756  $req->username = $user->getName();
1757  $req->returnToUrl = $returnToUrl;
1758  }
1759 
1760  $this->removeAuthenticationSessionData( null );
1761 
1762  $providers = $this->getPreAuthenticationProviders();
1763  foreach ( $providers as $id => $provider ) {
1764  $status = $provider->testForAccountLink( $user );
1765  if ( !$status->isGood() ) {
1766  $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1767  'user' => $user->getName(),
1768  ] );
1770  Status::wrap( $status )->getMessage()
1771  );
1772  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1773  return $ret;
1774  }
1775  }
1776 
1777  $state = [
1778  'username' => $user->getName(),
1779  'userid' => $user->getId(),
1780  'returnToUrl' => $returnToUrl,
1781  'primary' => null,
1782  'continueRequests' => [],
1783  ];
1784 
1785  $providers = $this->getPrimaryAuthenticationProviders();
1786  foreach ( $providers as $id => $provider ) {
1787  if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1788  continue;
1789  }
1790 
1791  $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1792  switch ( $res->status ) {
1794  $this->logger->info( "Account linked to {user} by $id", [
1795  'user' => $user->getName(),
1796  ] );
1797  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1798  return $res;
1799 
1801  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1802  'user' => $user->getName(),
1803  ] );
1804  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1805  return $res;
1806 
1808  // Continue loop
1809  break;
1810 
1813  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1814  'user' => $user->getName(),
1815  ] );
1816  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1817  $state['primary'] = $id;
1818  $state['continueRequests'] = $res->neededRequests;
1819  $session->setSecret( 'AuthManager::accountLinkState', $state );
1820  $session->persist();
1821  return $res;
1822 
1823  // @codeCoverageIgnoreStart
1824  default:
1825  throw new \DomainException(
1826  get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1827  );
1828  // @codeCoverageIgnoreEnd
1829  }
1830  }
1831 
1832  $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1833  'user' => $user->getName(),
1834  ] );
1836  wfMessage( 'authmanager-link-no-primary' )
1837  );
1838  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1839  return $ret;
1840  }
1841 
1847  public function continueAccountLink( array $reqs ) {
1848  $session = $this->request->getSession();
1849  try {
1850  if ( !$this->canLinkAccounts() ) {
1851  // Caller should have called canLinkAccounts()
1852  $session->remove( 'AuthManager::accountLinkState' );
1853  throw new \LogicException( 'Account linking is not possible' );
1854  }
1855 
1856  $state = $session->getSecret( 'AuthManager::accountLinkState' );
1857  if ( !is_array( $state ) ) {
1859  wfMessage( 'authmanager-link-not-in-progress' )
1860  );
1861  }
1862  $state['continueRequests'] = [];
1863 
1864  // Step 0: Prepare and validate the input
1865 
1866  $user = User::newFromName( $state['username'], 'usable' );
1867  if ( !is_object( $user ) ) {
1868  $session->remove( 'AuthManager::accountLinkState' );
1869  return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1870  }
1871  if ( $user->getId() != $state['userid'] ) {
1872  throw new \UnexpectedValueException(
1873  "User \"{$state['username']}\" is valid, but " .
1874  "ID {$user->getId()} != {$state['userid']}!"
1875  );
1876  }
1877 
1878  foreach ( $reqs as $req ) {
1879  $req->username = $state['username'];
1880  $req->returnToUrl = $state['returnToUrl'];
1881  }
1882 
1883  // Step 1: Call the primary again until it succeeds
1884 
1885  $provider = $this->getAuthenticationProvider( $state['primary'] );
1886  if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1887  // Configuration changed? Force them to start over.
1888  // @codeCoverageIgnoreStart
1890  wfMessage( 'authmanager-link-not-in-progress' )
1891  );
1892  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1893  $session->remove( 'AuthManager::accountLinkState' );
1894  return $ret;
1895  // @codeCoverageIgnoreEnd
1896  }
1897  $id = $provider->getUniqueId();
1898  $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1899  switch ( $res->status ) {
1901  $this->logger->info( "Account linked to {user} by $id", [
1902  'user' => $user->getName(),
1903  ] );
1904  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1905  $session->remove( 'AuthManager::accountLinkState' );
1906  return $res;
1908  $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1909  'user' => $user->getName(),
1910  ] );
1911  $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1912  $session->remove( 'AuthManager::accountLinkState' );
1913  return $res;
1916  $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1917  'user' => $user->getName(),
1918  ] );
1919  $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1920  $state['continueRequests'] = $res->neededRequests;
1921  $session->setSecret( 'AuthManager::accountLinkState', $state );
1922  return $res;
1923  default:
1924  throw new \DomainException(
1925  get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
1926  );
1927  }
1928  } catch ( \Exception $ex ) {
1929  $session->remove( 'AuthManager::accountLinkState' );
1930  throw $ex;
1931  }
1932  }
1933 
1959  public function getAuthenticationRequests( $action, User $user = null ) {
1960  $options = [];
1961  $providerAction = $action;
1962 
1963  // Figure out which providers to query
1964  switch ( $action ) {
1965  case self::ACTION_LOGIN:
1966  case self::ACTION_CREATE:
1967  $providers = $this->getPreAuthenticationProviders() +
1970  break;
1971 
1972  case self::ACTION_LOGIN_CONTINUE:
1973  $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
1974  return is_array( $state ) ? $state['continueRequests'] : [];
1975 
1976  case self::ACTION_CREATE_CONTINUE:
1977  $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
1978  return is_array( $state ) ? $state['continueRequests'] : [];
1979 
1980  case self::ACTION_LINK:
1981  $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
1982  return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
1983  } );
1984  break;
1985 
1986  case self::ACTION_UNLINK:
1987  $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
1988  return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
1989  } );
1990 
1991  // To providers, unlink and remove are identical.
1992  $providerAction = self::ACTION_REMOVE;
1993  break;
1994 
1995  case self::ACTION_LINK_CONTINUE:
1996  $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
1997  return is_array( $state ) ? $state['continueRequests'] : [];
1998 
1999  case self::ACTION_CHANGE:
2000  case self::ACTION_REMOVE:
2001  $providers = $this->getPrimaryAuthenticationProviders() +
2003  break;
2004 
2005  // @codeCoverageIgnoreStart
2006  default:
2007  throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2008  }
2009  // @codeCoverageIgnoreEnd
2010 
2011  return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2012  }
2013 
2024  $providerAction, array $options, array $providers, User $user = null
2025  ) {
2026  $user = $user ?: \RequestContext::getMain()->getUser();
2027  $options['username'] = $user->isAnon() ? null : $user->getName();
2028 
2029  // Query them and merge results
2030  $reqs = [];
2031  $allPrimaryRequired = null;
2032  foreach ( $providers as $provider ) {
2033  $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2034  $thisRequired = [];
2035  foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2036  $id = $req->getUniqueId();
2037 
2038  // If it's from a Primary, mark it as "primary-required" but
2039  // track it for later.
2040  if ( $isPrimary ) {
2041  if ( $req->required ) {
2042  $thisRequired[$id] = true;
2044  }
2045  }
2046 
2047  if ( !isset( $reqs[$id] ) || $req->required === AuthenticationRequest::REQUIRED ) {
2048  $reqs[$id] = $req;
2049  }
2050  }
2051 
2052  // Track which requests are required by all primaries
2053  if ( $isPrimary ) {
2054  $allPrimaryRequired = $allPrimaryRequired === null
2055  ? $thisRequired
2056  : array_intersect_key( $allPrimaryRequired, $thisRequired );
2057  }
2058  }
2059  // Any requests that were required by all primaries are required.
2060  foreach ( (array)$allPrimaryRequired as $id => $dummy ) {
2061  $reqs[$id]->required = AuthenticationRequest::REQUIRED;
2062  }
2063 
2064  // AuthManager has its own req for some actions
2065  switch ( $providerAction ) {
2066  case self::ACTION_LOGIN:
2067  $reqs[] = new RememberMeAuthenticationRequest;
2068  break;
2069 
2070  case self::ACTION_CREATE:
2071  $reqs[] = new UsernameAuthenticationRequest;
2072  $reqs[] = new UserDataAuthenticationRequest;
2073  if ( $options['username'] !== null ) {
2075  $options['username'] = null; // Don't fill in the username below
2076  }
2077  break;
2078  }
2079 
2080  // Fill in reqs data
2081  $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2082 
2083  // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2084  if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2085  $reqs = array_filter( $reqs, function ( $req ) {
2086  return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2087  } );
2088  }
2089 
2090  return array_values( $reqs );
2091  }
2092 
2100  private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2101  foreach ( $reqs as $req ) {
2102  if ( !$req->action || $forceAction ) {
2103  $req->action = $action;
2104  }
2105  if ( $req->username === null ) {
2106  $req->username = $username;
2107  }
2108  }
2109  }
2110 
2118  foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2119  if ( $provider->testUserExists( $username, $flags ) ) {
2120  return true;
2121  }
2122  }
2123 
2124  return false;
2125  }
2126 
2138  public function allowsPropertyChange( $property ) {
2139  $providers = $this->getPrimaryAuthenticationProviders() +
2141  foreach ( $providers as $provider ) {
2142  if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2143  return false;
2144  }
2145  }
2146  return true;
2147  }
2148 
2157  public function getAuthenticationProvider( $id ) {
2158  // Fast version
2159  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2160  return $this->allAuthenticationProviders[$id];
2161  }
2162 
2163  // Slow version: instantiate each kind and check
2164  $providers = $this->getPrimaryAuthenticationProviders();
2165  if ( isset( $providers[$id] ) ) {
2166  return $providers[$id];
2167  }
2168  $providers = $this->getSecondaryAuthenticationProviders();
2169  if ( isset( $providers[$id] ) ) {
2170  return $providers[$id];
2171  }
2172  $providers = $this->getPreAuthenticationProviders();
2173  if ( isset( $providers[$id] ) ) {
2174  return $providers[$id];
2175  }
2176 
2177  return null;
2178  }
2179 
2193  public function setAuthenticationSessionData( $key, $data ) {
2194  $session = $this->request->getSession();
2195  $arr = $session->getSecret( 'authData' );
2196  if ( !is_array( $arr ) ) {
2197  $arr = [];
2198  }
2199  $arr[$key] = $data;
2200  $session->setSecret( 'authData', $arr );
2201  }
2202 
2210  public function getAuthenticationSessionData( $key, $default = null ) {
2211  $arr = $this->request->getSession()->getSecret( 'authData' );
2212  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2213  return $arr[$key];
2214  } else {
2215  return $default;
2216  }
2217  }
2218 
2224  public function removeAuthenticationSessionData( $key ) {
2225  $session = $this->request->getSession();
2226  if ( $key === null ) {
2227  $session->remove( 'authData' );
2228  } else {
2229  $arr = $session->getSecret( 'authData' );
2230  if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2231  unset( $arr[$key] );
2232  $session->setSecret( 'authData', $arr );
2233  }
2234  }
2235  }
2236 
2243  protected function providerArrayFromSpecs( $class, array $specs ) {
2244  $i = 0;
2245  foreach ( $specs as &$spec ) {
2246  $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2247  }
2248  unset( $spec );
2249  usort( $specs, function ( $a, $b ) {
2250  return ( (int)$a['sort'] ) - ( (int)$b['sort'] )
2251  ?: $a['sort2'] - $b['sort2'];
2252  } );
2253 
2254  $ret = [];
2255  foreach ( $specs as $spec ) {
2256  $provider = \ObjectFactory::getObjectFromSpec( $spec );
2257  if ( !$provider instanceof $class ) {
2258  throw new \RuntimeException(
2259  "Expected instance of $class, got " . get_class( $provider )
2260  );
2261  }
2262  $provider->setLogger( $this->logger );
2263  $provider->setManager( $this );
2264  $provider->setConfig( $this->config );
2265  $id = $provider->getUniqueId();
2266  if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2267  throw new \RuntimeException(
2268  "Duplicate specifications for id $id (classes " .
2269  get_class( $provider ) . ' and ' .
2270  get_class( $this->allAuthenticationProviders[$id] ) . ')'
2271  );
2272  }
2273  $this->allAuthenticationProviders[$id] = $provider;
2274  $ret[$id] = $provider;
2275  }
2276  return $ret;
2277  }
2278 
2283  private function getConfiguration() {
2284  return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2285  }
2286 
2291  protected function getPreAuthenticationProviders() {
2292  if ( $this->preAuthenticationProviders === null ) {
2293  $conf = $this->getConfiguration();
2294  $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2295  PreAuthenticationProvider::class, $conf['preauth']
2296  );
2297  }
2299  }
2300 
2305  protected function getPrimaryAuthenticationProviders() {
2306  if ( $this->primaryAuthenticationProviders === null ) {
2307  $conf = $this->getConfiguration();
2308  $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2309  PrimaryAuthenticationProvider::class, $conf['primaryauth']
2310  );
2311  }
2313  }
2314 
2320  if ( $this->secondaryAuthenticationProviders === null ) {
2321  $conf = $this->getConfiguration();
2322  $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2323  SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2324  );
2325  }
2327  }
2328 
2333  private function setSessionDataForUser( $user, $remember = null ) {
2334  $session = $this->request->getSession();
2335  $delay = $session->delaySave();
2336 
2337  $session->resetId();
2338  $session->resetAllTokens();
2339  if ( $session->canSetUser() ) {
2340  $session->setUser( $user );
2341  }
2342  if ( $remember !== null ) {
2343  $session->setRememberUser( $remember );
2344  }
2345  $session->set( 'AuthManager:lastAuthId', $user->getId() );
2346  $session->set( 'AuthManager:lastAuthTimestamp', time() );
2347  $session->persist();
2348 
2349  \ScopedCallback::consume( $delay );
2350 
2351  \Hooks::run( 'UserLoggedIn', [ $user ] );
2352  }
2353 
2358  private function setDefaultUserOptions( User $user, $useContextLang ) {
2360 
2361  $user->setToken();
2362 
2363  $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $wgContLang;
2364  $user->setOption( 'language', $lang->getPreferredVariant() );
2365 
2366  if ( $wgContLang->hasVariants() ) {
2367  $user->setOption( 'variant', $wgContLang->getPreferredVariant() );
2368  }
2369  }
2370 
2376  private function callMethodOnProviders( $which, $method, array $args ) {
2377  $providers = [];
2378  if ( $which & 1 ) {
2379  $providers += $this->getPreAuthenticationProviders();
2380  }
2381  if ( $which & 2 ) {
2382  $providers += $this->getPrimaryAuthenticationProviders();
2383  }
2384  if ( $which & 4 ) {
2385  $providers += $this->getSecondaryAuthenticationProviders();
2386  }
2387  foreach ( $providers as $provider ) {
2388  call_user_func_array( [ $provider, $method ], $args );
2389  }
2390  }
2391 
2395  public static function resetCache() {
2396  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2397  // @codeCoverageIgnoreStart
2398  throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2399  // @codeCoverageIgnoreEnd
2400  }
2401 
2402  self::$instance = null;
2403  }
2404 
2407 }
2408 
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:522
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, but other primary authen...
the array() calling protocol came about after MediaWiki 1.4rc1.
static wrap($sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:79
canCreateAccount($username, $options=[])
Determine whether a particular account can be created.
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:1706
saveSettings()
Save this user's settings into the database.
Definition: User.php:3942
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:80
userCanAuthenticate($username)
Determine whether a username can authenticate.
static instance()
Singleton.
Definition: Profiler.php:60
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:37
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value...
Definition: Status.php:40
static isUsableName($name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition: User.php:889
$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:545
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:2588
continueAccountCreation(array $reqs)
Continue an account creation flow.
The MediaWiki class is the helper class for the index.php entry point.
Definition: MediaWiki.php:28
Authentication request for the reason given for account creation.
$wgAuth $wgAuth
Authentication plugin.
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:2707
setOption($oname, $val)
Set the given option for a user.
Definition: User.php:3002
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2139
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.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
$last
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition: User.php:4215
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.
canLinkAccounts()
Determine whether accounts can be linked.
Interface for configuration instances.
Definition: Config.php:28
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:1020
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
revokeAccessForUser($username)
Revoke any authentication credentials for a user.
$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.
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 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 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
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value...
Definition: StatusValue.php:42
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.
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:1816
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:2130
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:981
this hook is for auditing only or null if authentication failed before getting that far $username
Definition: hooks.txt:776
wfGetLBFactory()
Get the load balancer factory object.
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:2114
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:4110
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
$wgDisableAuthManager
Disable AuthManager.
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:964
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:4255
static idFromName($name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition: User.php:764
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:1020
__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:420
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:3654
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:1994
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