MediaWiki REL1_28
AuthManager.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Auth;
25
27use Psr\Log\LoggerAwareInterface;
28use Psr\Log\LoggerInterface;
31use User;
33
81class AuthManager implements LoggerAwareInterface {
83 const ACTION_LOGIN = 'login';
86 const ACTION_LOGIN_CONTINUE = 'login-continue';
88 const ACTION_CREATE = 'create';
91 const ACTION_CREATE_CONTINUE = 'create-continue';
93 const ACTION_LINK = 'link';
96 const ACTION_LINK_CONTINUE = 'link-continue';
98 const ACTION_CHANGE = 'change';
100 const ACTION_REMOVE = 'remove';
102 const ACTION_UNLINK = 'unlink';
103
105 const SEC_OK = 'ok';
107 const SEC_REAUTH = 'reauth';
109 const SEC_FAIL = 'fail';
110
112 const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
113
115 private static $instance = null;
116
118 private $request;
119
121 private $config;
122
124 private $logger;
125
128
131
134
137
140
145 public static function singleton() {
146 if ( self::$instance === null ) {
147 self::$instance = new self(
148 \RequestContext::getMain()->getRequest(),
149 \ConfigFactory::getDefaultInstance()->makeConfig( 'main' )
150 );
151 }
152 return self::$instance;
153 }
154
160 $this->request = $request;
161 $this->config = $config;
162 $this->setLogger( \MediaWiki\Logger\LoggerFactory::getInstance( 'authentication' ) );
163 }
164
168 public function setLogger( LoggerInterface $logger ) {
169 $this->logger = $logger;
170 }
171
175 public function getRequest() {
176 return $this->request;
177 }
178
185 public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
186 $this->logger->warning( "Overriding AuthManager primary authn because $why" );
187
188 if ( $this->primaryAuthenticationProviders !== null ) {
189 $this->logger->warning(
190 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
191 );
192
193 $this->allAuthenticationProviders = array_diff_key(
194 $this->allAuthenticationProviders,
195 $this->primaryAuthenticationProviders
196 );
197 $session = $this->request->getSession();
198 $session->remove( 'AuthManager::authnState' );
199 $session->remove( 'AuthManager::accountCreationState' );
200 $session->remove( 'AuthManager::accountLinkState' );
201 $this->createdAccountAuthenticationRequests = [];
202 }
203
204 $this->primaryAuthenticationProviders = [];
205 foreach ( $providers as $provider ) {
206 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
207 throw new \RuntimeException(
208 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
209 get_class( $provider )
210 );
211 }
212 $provider->setLogger( $this->logger );
213 $provider->setManager( $this );
214 $provider->setConfig( $this->config );
215 $id = $provider->getUniqueId();
216 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
217 throw new \RuntimeException(
218 "Duplicate specifications for id $id (classes " .
219 get_class( $provider ) . ' and ' .
220 get_class( $this->allAuthenticationProviders[$id] ) . ')'
221 );
222 }
223 $this->allAuthenticationProviders[$id] = $provider;
224 $this->primaryAuthenticationProviders[$id] = $provider;
225 }
226 }
227
237 public static function callLegacyAuthPlugin( $method, array $params, $return = null ) {
239
240 if ( $wgAuth && !$wgAuth instanceof AuthManagerAuthPlugin ) {
241 return call_user_func_array( [ $wgAuth, $method ], $params );
242 } else {
243 return $return;
244 }
245 }
246
260 public function canAuthenticateNow() {
261 return $this->request->getSession()->canSetUser();
262 }
263
282 public function beginAuthentication( array $reqs, $returnToUrl ) {
283 $session = $this->request->getSession();
284 if ( !$session->canSetUser() ) {
285 // Caller should have called canAuthenticateNow()
286 $session->remove( 'AuthManager::authnState' );
287 throw new \LogicException( 'Authentication is not possible now' );
288 }
289
290 $guessUserName = null;
291 foreach ( $reqs as $req ) {
292 $req->returnToUrl = $returnToUrl;
293 // @codeCoverageIgnoreStart
294 if ( $req->username !== null && $req->username !== '' ) {
295 if ( $guessUserName === null ) {
296 $guessUserName = $req->username;
297 } elseif ( $guessUserName !== $req->username ) {
298 $guessUserName = null;
299 break;
300 }
301 }
302 // @codeCoverageIgnoreEnd
303 }
304
305 // Check for special-case login of a just-created account
307 $reqs, CreatedAccountAuthenticationRequest::class
308 );
309 if ( $req ) {
310 if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
311 throw new \LogicException(
312 'CreatedAccountAuthenticationRequests are only valid on ' .
313 'the same AuthManager that created the account'
314 );
315 }
316
317 $user = User::newFromName( $req->username );
318 // @codeCoverageIgnoreStart
319 if ( !$user ) {
320 throw new \UnexpectedValueException(
321 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
322 );
323 } elseif ( $user->getId() != $req->id ) {
324 throw new \UnexpectedValueException(
325 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
326 );
327 }
328 // @codeCoverageIgnoreEnd
329
330 $this->logger->info( 'Logging in {user} after account creation', [
331 'user' => $user->getName(),
332 ] );
335 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
336 $session->remove( 'AuthManager::authnState' );
337 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
338 return $ret;
339 }
340
341 $this->removeAuthenticationSessionData( null );
342
343 foreach ( $this->getPreAuthenticationProviders() as $provider ) {
344 $status = $provider->testForAuthentication( $reqs );
345 if ( !$status->isGood() ) {
346 $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
348 Status::wrap( $status )->getMessage()
349 );
350 $this->callMethodOnProviders( 7, 'postAuthentication',
351 [ User::newFromName( $guessUserName ) ?: null, $ret ]
352 );
353 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, null, $guessUserName ] );
354 return $ret;
355 }
356 }
357
358 $state = [
359 'reqs' => $reqs,
360 'returnToUrl' => $returnToUrl,
361 'guessUserName' => $guessUserName,
362 'primary' => null,
363 'primaryResponse' => null,
364 'secondary' => [],
365 'maybeLink' => [],
366 'continueRequests' => [],
367 ];
368
369 // Preserve state from a previous failed login
371 $reqs, CreateFromLoginAuthenticationRequest::class
372 );
373 if ( $req ) {
374 $state['maybeLink'] = $req->maybeLink;
375 }
376
377 $session = $this->request->getSession();
378 $session->setSecret( 'AuthManager::authnState', $state );
379 $session->persist();
380
381 return $this->continueAuthentication( $reqs );
382 }
383
406 public function continueAuthentication( array $reqs ) {
407 $session = $this->request->getSession();
408 try {
409 if ( !$session->canSetUser() ) {
410 // Caller should have called canAuthenticateNow()
411 // @codeCoverageIgnoreStart
412 throw new \LogicException( 'Authentication is not possible now' );
413 // @codeCoverageIgnoreEnd
414 }
415
416 $state = $session->getSecret( 'AuthManager::authnState' );
417 if ( !is_array( $state ) ) {
419 wfMessage( 'authmanager-authn-not-in-progress' )
420 );
421 }
422 $state['continueRequests'] = [];
423
424 $guessUserName = $state['guessUserName'];
425
426 foreach ( $reqs as $req ) {
427 $req->returnToUrl = $state['returnToUrl'];
428 }
429
430 // Step 1: Choose an primary authentication provider, and call it until it succeeds.
431
432 if ( $state['primary'] === null ) {
433 // We haven't picked a PrimaryAuthenticationProvider yet
434 // @codeCoverageIgnoreStart
435 $guessUserName = null;
436 foreach ( $reqs as $req ) {
437 if ( $req->username !== null && $req->username !== '' ) {
438 if ( $guessUserName === null ) {
439 $guessUserName = $req->username;
440 } elseif ( $guessUserName !== $req->username ) {
441 $guessUserName = null;
442 break;
443 }
444 }
445 }
446 $state['guessUserName'] = $guessUserName;
447 // @codeCoverageIgnoreEnd
448 $state['reqs'] = $reqs;
449
450 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
451 $res = $provider->beginPrimaryAuthentication( $reqs );
452 switch ( $res->status ) {
454 $state['primary'] = $id;
455 $state['primaryResponse'] = $res;
456 $this->logger->debug( "Primary login with $id succeeded" );
457 break 2;
459 $this->logger->debug( "Login failed in primary authentication by $id" );
460 if ( $res->createRequest || $state['maybeLink'] ) {
461 $res->createRequest = new CreateFromLoginAuthenticationRequest(
462 $res->createRequest, $state['maybeLink']
463 );
464 }
465 $this->callMethodOnProviders( 7, 'postAuthentication',
466 [ User::newFromName( $guessUserName ) ?: null, $res ]
467 );
468 $session->remove( 'AuthManager::authnState' );
469 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
470 return $res;
472 // Continue loop
473 break;
476 $this->logger->debug( "Primary login with $id returned $res->status" );
477 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
478 $state['primary'] = $id;
479 $state['continueRequests'] = $res->neededRequests;
480 $session->setSecret( 'AuthManager::authnState', $state );
481 return $res;
482
483 // @codeCoverageIgnoreStart
484 default:
485 throw new \DomainException(
486 get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
487 );
488 // @codeCoverageIgnoreEnd
489 }
490 }
491 if ( $state['primary'] === null ) {
492 $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
494 wfMessage( 'authmanager-authn-no-primary' )
495 );
496 $this->callMethodOnProviders( 7, 'postAuthentication',
497 [ User::newFromName( $guessUserName ) ?: null, $ret ]
498 );
499 $session->remove( 'AuthManager::authnState' );
500 return $ret;
501 }
502 } elseif ( $state['primaryResponse'] === null ) {
503 $provider = $this->getAuthenticationProvider( $state['primary'] );
504 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
505 // Configuration changed? Force them to start over.
506 // @codeCoverageIgnoreStart
508 wfMessage( 'authmanager-authn-not-in-progress' )
509 );
510 $this->callMethodOnProviders( 7, 'postAuthentication',
511 [ User::newFromName( $guessUserName ) ?: null, $ret ]
512 );
513 $session->remove( 'AuthManager::authnState' );
514 return $ret;
515 // @codeCoverageIgnoreEnd
516 }
517 $id = $provider->getUniqueId();
518 $res = $provider->continuePrimaryAuthentication( $reqs );
519 switch ( $res->status ) {
521 $state['primaryResponse'] = $res;
522 $this->logger->debug( "Primary login with $id succeeded" );
523 break;
525 $this->logger->debug( "Login failed in primary authentication by $id" );
526 if ( $res->createRequest || $state['maybeLink'] ) {
527 $res->createRequest = new CreateFromLoginAuthenticationRequest(
528 $res->createRequest, $state['maybeLink']
529 );
530 }
531 $this->callMethodOnProviders( 7, 'postAuthentication',
532 [ User::newFromName( $guessUserName ) ?: null, $res ]
533 );
534 $session->remove( 'AuthManager::authnState' );
535 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, null, $guessUserName ] );
536 return $res;
539 $this->logger->debug( "Primary login with $id returned $res->status" );
540 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
541 $state['continueRequests'] = $res->neededRequests;
542 $session->setSecret( 'AuthManager::authnState', $state );
543 return $res;
544 default:
545 throw new \DomainException(
546 get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
547 );
548 }
549 }
550
551 $res = $state['primaryResponse'];
552 if ( $res->username === null ) {
553 $provider = $this->getAuthenticationProvider( $state['primary'] );
554 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
555 // Configuration changed? Force them to start over.
556 // @codeCoverageIgnoreStart
558 wfMessage( 'authmanager-authn-not-in-progress' )
559 );
560 $this->callMethodOnProviders( 7, 'postAuthentication',
561 [ User::newFromName( $guessUserName ) ?: null, $ret ]
562 );
563 $session->remove( 'AuthManager::authnState' );
564 return $ret;
565 // @codeCoverageIgnoreEnd
566 }
567
568 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
569 $res->linkRequest &&
570 // don't confuse the user with an incorrect message if linking is disabled
571 $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
572 ) {
573 $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
574 $msg = 'authmanager-authn-no-local-user-link';
575 } else {
576 $msg = 'authmanager-authn-no-local-user';
577 }
578 $this->logger->debug(
579 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
580 );
582 $ret->neededRequests = $this->getAuthenticationRequestsInternal(
583 self::ACTION_LOGIN,
584 [],
586 );
587 if ( $res->createRequest || $state['maybeLink'] ) {
588 $ret->createRequest = new CreateFromLoginAuthenticationRequest(
589 $res->createRequest, $state['maybeLink']
590 );
591 $ret->neededRequests[] = $ret->createRequest;
592 }
593 $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
594 $session->setSecret( 'AuthManager::authnState', [
595 'reqs' => [], // Will be filled in later
596 'primary' => null,
597 'primaryResponse' => null,
598 'secondary' => [],
599 'continueRequests' => $ret->neededRequests,
600 ] + $state );
601 return $ret;
602 }
603
604 // Step 2: Primary authentication succeeded, create the User object
605 // (and add the user locally if necessary)
606
607 $user = User::newFromName( $res->username, 'usable' );
608 if ( !$user ) {
609 $provider = $this->getAuthenticationProvider( $state['primary'] );
610 throw new \DomainException(
611 get_class( $provider ) . " returned an invalid username: {$res->username}"
612 );
613 }
614 if ( $user->getId() === 0 ) {
615 // User doesn't exist locally. Create it.
616 $this->logger->info( 'Auto-creating {user} on login', [
617 'user' => $user->getName(),
618 ] );
619 $status = $this->autoCreateUser( $user, $state['primary'], false );
620 if ( !$status->isGood() ) {
622 Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
623 );
624 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
625 $session->remove( 'AuthManager::authnState' );
626 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
627 return $ret;
628 }
629 }
630
631 // Step 3: Iterate over all the secondary authentication providers.
632
633 $beginReqs = $state['reqs'];
634
635 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
636 if ( !isset( $state['secondary'][$id] ) ) {
637 // This provider isn't started yet, so we pass it the set
638 // of reqs from beginAuthentication instead of whatever
639 // might have been used by a previous provider in line.
640 $func = 'beginSecondaryAuthentication';
641 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
642 } elseif ( !$state['secondary'][$id] ) {
643 $func = 'continueSecondaryAuthentication';
644 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
645 } else {
646 continue;
647 }
648 switch ( $res->status ) {
650 $this->logger->debug( "Secondary login with $id succeeded" );
651 // fall through
653 $state['secondary'][$id] = true;
654 break;
656 $this->logger->debug( "Login failed in secondary authentication by $id" );
657 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
658 $session->remove( 'AuthManager::authnState' );
659 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $res, $user, $user->getName() ] );
660 return $res;
663 $this->logger->debug( "Secondary login with $id returned " . $res->status );
664 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
665 $state['secondary'][$id] = false;
666 $state['continueRequests'] = $res->neededRequests;
667 $session->setSecret( 'AuthManager::authnState', $state );
668 return $res;
669
670 // @codeCoverageIgnoreStart
671 default:
672 throw new \DomainException(
673 get_class( $provider ) . "::{$func}() returned $res->status"
674 );
675 // @codeCoverageIgnoreEnd
676 }
677 }
678
679 // Step 4: Authentication complete! Set the user in the session and
680 // clean up.
681
682 $this->logger->info( 'Login for {user} succeeded', [
683 'user' => $user->getName(),
684 ] );
687 $beginReqs, RememberMeAuthenticationRequest::class
688 );
689 $this->setSessionDataForUser( $user, $req && $req->rememberMe );
691 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
692 $session->remove( 'AuthManager::authnState' );
693 $this->removeAuthenticationSessionData( null );
694 \Hooks::run( 'AuthManagerLoginAuthenticateAudit', [ $ret, $user, $user->getName() ] );
695 return $ret;
696 } catch ( \Exception $ex ) {
697 $session->remove( 'AuthManager::authnState' );
698 throw $ex;
699 }
700 }
701
713 public function securitySensitiveOperationStatus( $operation ) {
715
716 $this->logger->debug( __METHOD__ . ": Checking $operation" );
717
718 $session = $this->request->getSession();
719 $aId = $session->getUser()->getId();
720 if ( $aId === 0 ) {
721 // User isn't authenticated. DWIM?
723 $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
724 return $status;
725 }
726
727 if ( $session->canSetUser() ) {
728 $id = $session->get( 'AuthManager:lastAuthId' );
729 $last = $session->get( 'AuthManager:lastAuthTimestamp' );
730 if ( $id !== $aId || $last === null ) {
731 $timeSinceLogin = PHP_INT_MAX; // Forever ago
732 } else {
733 $timeSinceLogin = max( 0, time() - $last );
734 }
735
736 $thresholds = $this->config->get( 'ReauthenticateTime' );
737 if ( isset( $thresholds[$operation] ) ) {
738 $threshold = $thresholds[$operation];
739 } elseif ( isset( $thresholds['default'] ) ) {
740 $threshold = $thresholds['default'];
741 } else {
742 throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
743 }
744
745 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
747 }
748 } else {
749 $timeSinceLogin = -1;
750
751 $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
752 if ( isset( $pass[$operation] ) ) {
753 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
754 } elseif ( isset( $pass['default'] ) ) {
755 $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
756 } else {
757 throw new \UnexpectedValueException(
758 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
759 );
760 }
761 }
762
763 \Hooks::run( 'SecuritySensitiveOperationStatus', [
764 &$status, $operation, $session, $timeSinceLogin
765 ] );
766
767 // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
768 if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
770 }
771
772 $this->logger->info( __METHOD__ . ": $operation is $status" );
773
774 return $status;
775 }
776
786 public function userCanAuthenticate( $username ) {
787 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
788 if ( $provider->testUserCanAuthenticate( $username ) ) {
789 return true;
790 }
791 }
792 return false;
793 }
794
809 public function normalizeUsername( $username ) {
810 $ret = [];
811 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
812 $normalized = $provider->providerNormalizeUsername( $username );
813 if ( $normalized !== null ) {
814 $ret[$normalized] = true;
815 }
816 }
817 return array_keys( $ret );
818 }
819
834 public function revokeAccessForUser( $username ) {
835 $this->logger->info( 'Revoking access for {user}', [
836 'user' => $username,
837 ] );
838 $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
839 }
840
850 public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
851 $any = false;
852 $providers = $this->getPrimaryAuthenticationProviders() +
854 foreach ( $providers as $provider ) {
855 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
856 if ( !$status->isGood() ) {
857 return Status::wrap( $status );
858 }
859 $any = $any || $status->value !== 'ignored';
860 }
861 if ( !$any ) {
862 $status = Status::newGood( 'ignored' );
863 $status->warning( 'authmanager-change-not-supported' );
864 return $status;
865 }
866 return Status::newGood();
867 }
868
884 $this->logger->info( 'Changing authentication data for {user} class {what}', [
885 'user' => is_string( $req->username ) ? $req->username : '<no name>',
886 'what' => get_class( $req ),
887 ] );
888
889 $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
890
891 // When the main account's authentication data is changed, invalidate
892 // all BotPasswords too.
893 \BotPassword::invalidateAllPasswordsForUser( $req->username );
894 }
895
907 public function canCreateAccounts() {
908 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
909 switch ( $provider->accountCreationType() ) {
912 return true;
913 }
914 }
915 return false;
916 }
917
926 public function canCreateAccount( $username, $options = [] ) {
927 // Back compat
928 if ( is_int( $options ) ) {
929 $options = [ 'flags' => $options ];
930 }
931 $options += [
932 'flags' => User::READ_NORMAL,
933 'creating' => false,
934 ];
935 $flags = $options['flags'];
936
937 if ( !$this->canCreateAccounts() ) {
938 return Status::newFatal( 'authmanager-create-disabled' );
939 }
940
941 if ( $this->userExists( $username, $flags ) ) {
942 return Status::newFatal( 'userexists' );
943 }
944
945 $user = User::newFromName( $username, 'creatable' );
946 if ( !is_object( $user ) ) {
947 return Status::newFatal( 'noname' );
948 } else {
949 $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
950 if ( $user->getId() !== 0 ) {
951 return Status::newFatal( 'userexists' );
952 }
953 }
954
955 // Denied by providers?
956 $providers = $this->getPreAuthenticationProviders() +
959 foreach ( $providers as $provider ) {
960 $status = $provider->testUserForCreation( $user, false, $options );
961 if ( !$status->isGood() ) {
962 return Status::wrap( $status );
963 }
964 }
965
966 return Status::newGood();
967 }
968
974 public function checkAccountCreatePermissions( User $creator ) {
975 // Wiki is read-only?
976 if ( wfReadOnly() ) {
977 return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
978 }
979
980 // This is awful, this permission check really shouldn't go through Title.
981 $permErrors = \SpecialPage::getTitleFor( 'CreateAccount' )
982 ->getUserPermissionsErrors( 'createaccount', $creator, 'secure' );
983 if ( $permErrors ) {
984 $status = Status::newGood();
985 foreach ( $permErrors as $args ) {
986 call_user_func_array( [ $status, 'fatal' ], $args );
987 }
988 return $status;
989 }
990
991 $block = $creator->isBlockedFromCreateAccount();
992 if ( $block ) {
993 $errorParams = [
994 $block->getTarget(),
995 $block->mReason ?: wfMessage( 'blockednoreason' )->text(),
996 $block->getByName()
997 ];
998
999 if ( $block->getType() === \Block::TYPE_RANGE ) {
1000 $errorMessage = 'cantcreateaccount-range-text';
1001 $errorParams[] = $this->getRequest()->getIP();
1002 } else {
1003 $errorMessage = 'cantcreateaccount-text';
1004 }
1005
1006 return Status::newFatal( wfMessage( $errorMessage, $errorParams ) );
1007 }
1008
1009 $ip = $this->getRequest()->getIP();
1010 if ( $creator->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
1011 return Status::newFatal( 'sorbs_create_account_reason' );
1012 }
1013
1014 return Status::newGood();
1015 }
1016
1036 public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
1037 $session = $this->request->getSession();
1038 if ( !$this->canCreateAccounts() ) {
1039 // Caller should have called canCreateAccounts()
1040 $session->remove( 'AuthManager::accountCreationState' );
1041 throw new \LogicException( 'Account creation is not possible' );
1042 }
1043
1044 try {
1046 } catch ( \UnexpectedValueException $ex ) {
1047 $username = null;
1048 }
1049 if ( $username === null ) {
1050 $this->logger->debug( __METHOD__ . ': No username provided' );
1051 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1052 }
1053
1054 // Permissions check
1055 $status = $this->checkAccountCreatePermissions( $creator );
1056 if ( !$status->isGood() ) {
1057 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1058 'user' => $username,
1059 'creator' => $creator->getName(),
1060 'reason' => $status->getWikiText( null, null, 'en' )
1061 ] );
1062 return AuthenticationResponse::newFail( $status->getMessage() );
1063 }
1064
1065 $status = $this->canCreateAccount(
1066 $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1067 );
1068 if ( !$status->isGood() ) {
1069 $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1070 'user' => $username,
1071 'creator' => $creator->getName(),
1072 'reason' => $status->getWikiText( null, null, 'en' )
1073 ] );
1074 return AuthenticationResponse::newFail( $status->getMessage() );
1075 }
1076
1077 $user = User::newFromName( $username, 'creatable' );
1078 foreach ( $reqs as $req ) {
1079 $req->username = $username;
1080 $req->returnToUrl = $returnToUrl;
1081 if ( $req instanceof UserDataAuthenticationRequest ) {
1082 $status = $req->populateUser( $user );
1083 if ( !$status->isGood() ) {
1084 $status = Status::wrap( $status );
1085 $session->remove( 'AuthManager::accountCreationState' );
1086 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1087 'user' => $user->getName(),
1088 'creator' => $creator->getName(),
1089 'reason' => $status->getWikiText( null, null, 'en' ),
1090 ] );
1091 return AuthenticationResponse::newFail( $status->getMessage() );
1092 }
1093 }
1094 }
1095
1096 $this->removeAuthenticationSessionData( null );
1097
1098 $state = [
1099 'username' => $username,
1100 'userid' => 0,
1101 'creatorid' => $creator->getId(),
1102 'creatorname' => $creator->getName(),
1103 'reqs' => $reqs,
1104 'returnToUrl' => $returnToUrl,
1105 'primary' => null,
1106 'primaryResponse' => null,
1107 'secondary' => [],
1108 'continueRequests' => [],
1109 'maybeLink' => [],
1110 'ranPreTests' => false,
1111 ];
1112
1113 // Special case: converting a login to an account creation
1115 $reqs, CreateFromLoginAuthenticationRequest::class
1116 );
1117 if ( $req ) {
1118 $state['maybeLink'] = $req->maybeLink;
1119
1120 if ( $req->createRequest ) {
1121 $reqs[] = $req->createRequest;
1122 $state['reqs'][] = $req->createRequest;
1123 }
1124 }
1125
1126 $session->setSecret( 'AuthManager::accountCreationState', $state );
1127 $session->persist();
1128
1129 return $this->continueAccountCreation( $reqs );
1130 }
1131
1137 public function continueAccountCreation( array $reqs ) {
1138 $session = $this->request->getSession();
1139 try {
1140 if ( !$this->canCreateAccounts() ) {
1141 // Caller should have called canCreateAccounts()
1142 $session->remove( 'AuthManager::accountCreationState' );
1143 throw new \LogicException( 'Account creation is not possible' );
1144 }
1145
1146 $state = $session->getSecret( 'AuthManager::accountCreationState' );
1147 if ( !is_array( $state ) ) {
1149 wfMessage( 'authmanager-create-not-in-progress' )
1150 );
1151 }
1152 $state['continueRequests'] = [];
1153
1154 // Step 0: Prepare and validate the input
1155
1156 $user = User::newFromName( $state['username'], 'creatable' );
1157 if ( !is_object( $user ) ) {
1158 $session->remove( 'AuthManager::accountCreationState' );
1159 $this->logger->debug( __METHOD__ . ': Invalid username', [
1160 'user' => $state['username'],
1161 ] );
1162 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1163 }
1164
1165 if ( $state['creatorid'] ) {
1166 $creator = User::newFromId( $state['creatorid'] );
1167 } else {
1168 $creator = new User;
1169 $creator->setName( $state['creatorname'] );
1170 }
1171
1172 // Avoid account creation races on double submissions
1173 $cache = \ObjectCache::getLocalClusterInstance();
1174 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1175 if ( !$lock ) {
1176 // Don't clear AuthManager::accountCreationState for this code
1177 // path because the process that won the race owns it.
1178 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1179 'user' => $user->getName(),
1180 'creator' => $creator->getName(),
1181 ] );
1182 return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1183 }
1184
1185 // Permissions check
1186 $status = $this->checkAccountCreatePermissions( $creator );
1187 if ( !$status->isGood() ) {
1188 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1189 'user' => $user->getName(),
1190 'creator' => $creator->getName(),
1191 'reason' => $status->getWikiText( null, null, 'en' )
1192 ] );
1193 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1194 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1195 $session->remove( 'AuthManager::accountCreationState' );
1196 return $ret;
1197 }
1198
1199 // Load from master for existence check
1200 $user->load( User::READ_LOCKING );
1201
1202 if ( $state['userid'] === 0 ) {
1203 if ( $user->getId() != 0 ) {
1204 $this->logger->debug( __METHOD__ . ': User exists locally', [
1205 'user' => $user->getName(),
1206 'creator' => $creator->getName(),
1207 ] );
1208 $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1209 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1210 $session->remove( 'AuthManager::accountCreationState' );
1211 return $ret;
1212 }
1213 } else {
1214 if ( $user->getId() == 0 ) {
1215 $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1216 'user' => $user->getName(),
1217 'creator' => $creator->getName(),
1218 'expected_id' => $state['userid'],
1219 ] );
1220 throw new \UnexpectedValueException(
1221 "User \"{$state['username']}\" should exist now, but doesn't!"
1222 );
1223 }
1224 if ( $user->getId() != $state['userid'] ) {
1225 $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1226 'user' => $user->getName(),
1227 'creator' => $creator->getName(),
1228 'expected_id' => $state['userid'],
1229 'actual_id' => $user->getId(),
1230 ] );
1231 throw new \UnexpectedValueException(
1232 "User \"{$state['username']}\" exists, but " .
1233 "ID {$user->getId()} != {$state['userid']}!"
1234 );
1235 }
1236 }
1237 foreach ( $state['reqs'] as $req ) {
1238 if ( $req instanceof UserDataAuthenticationRequest ) {
1239 $status = $req->populateUser( $user );
1240 if ( !$status->isGood() ) {
1241 // This should never happen...
1242 $status = Status::wrap( $status );
1243 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1244 'user' => $user->getName(),
1245 'creator' => $creator->getName(),
1246 'reason' => $status->getWikiText( null, null, 'en' ),
1247 ] );
1248 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1249 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1250 $session->remove( 'AuthManager::accountCreationState' );
1251 return $ret;
1252 }
1253 }
1254 }
1255
1256 foreach ( $reqs as $req ) {
1257 $req->returnToUrl = $state['returnToUrl'];
1258 $req->username = $state['username'];
1259 }
1260
1261 // Run pre-creation tests, if we haven't already
1262 if ( !$state['ranPreTests'] ) {
1263 $providers = $this->getPreAuthenticationProviders() +
1266 foreach ( $providers as $id => $provider ) {
1267 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1268 if ( !$status->isGood() ) {
1269 $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1270 'user' => $user->getName(),
1271 'creator' => $creator->getName(),
1272 ] );
1274 Status::wrap( $status )->getMessage()
1275 );
1276 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1277 $session->remove( 'AuthManager::accountCreationState' );
1278 return $ret;
1279 }
1280 }
1281
1282 $state['ranPreTests'] = true;
1283 }
1284
1285 // Step 1: Choose a primary authentication provider and call it until it succeeds.
1286
1287 if ( $state['primary'] === null ) {
1288 // We haven't picked a PrimaryAuthenticationProvider yet
1289 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1290 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1291 continue;
1292 }
1293 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1294 switch ( $res->status ) {
1296 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1297 'user' => $user->getName(),
1298 'creator' => $creator->getName(),
1299 ] );
1300 $state['primary'] = $id;
1301 $state['primaryResponse'] = $res;
1302 break 2;
1304 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1305 'user' => $user->getName(),
1306 'creator' => $creator->getName(),
1307 ] );
1308 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1309 $session->remove( 'AuthManager::accountCreationState' );
1310 return $res;
1312 // Continue loop
1313 break;
1316 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1317 'user' => $user->getName(),
1318 'creator' => $creator->getName(),
1319 ] );
1320 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1321 $state['primary'] = $id;
1322 $state['continueRequests'] = $res->neededRequests;
1323 $session->setSecret( 'AuthManager::accountCreationState', $state );
1324 return $res;
1325
1326 // @codeCoverageIgnoreStart
1327 default:
1328 throw new \DomainException(
1329 get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1330 );
1331 // @codeCoverageIgnoreEnd
1332 }
1333 }
1334 if ( $state['primary'] === null ) {
1335 $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1336 'user' => $user->getName(),
1337 'creator' => $creator->getName(),
1338 ] );
1340 wfMessage( 'authmanager-create-no-primary' )
1341 );
1342 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1343 $session->remove( 'AuthManager::accountCreationState' );
1344 return $ret;
1345 }
1346 } elseif ( $state['primaryResponse'] === null ) {
1347 $provider = $this->getAuthenticationProvider( $state['primary'] );
1348 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1349 // Configuration changed? Force them to start over.
1350 // @codeCoverageIgnoreStart
1352 wfMessage( 'authmanager-create-not-in-progress' )
1353 );
1354 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1355 $session->remove( 'AuthManager::accountCreationState' );
1356 return $ret;
1357 // @codeCoverageIgnoreEnd
1358 }
1359 $id = $provider->getUniqueId();
1360 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1361 switch ( $res->status ) {
1363 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1364 'user' => $user->getName(),
1365 'creator' => $creator->getName(),
1366 ] );
1367 $state['primaryResponse'] = $res;
1368 break;
1370 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1371 'user' => $user->getName(),
1372 'creator' => $creator->getName(),
1373 ] );
1374 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1375 $session->remove( 'AuthManager::accountCreationState' );
1376 return $res;
1379 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1380 'user' => $user->getName(),
1381 'creator' => $creator->getName(),
1382 ] );
1383 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1384 $state['continueRequests'] = $res->neededRequests;
1385 $session->setSecret( 'AuthManager::accountCreationState', $state );
1386 return $res;
1387 default:
1388 throw new \DomainException(
1389 get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1390 );
1391 }
1392 }
1393
1394 // Step 2: Primary authentication succeeded, create the User object
1395 // and add the user locally.
1396
1397 if ( $state['userid'] === 0 ) {
1398 $this->logger->info( 'Creating user {user} during account creation', [
1399 'user' => $user->getName(),
1400 'creator' => $creator->getName(),
1401 ] );
1402 $status = $user->addToDatabase();
1403 if ( !$status->isOK() ) {
1404 // @codeCoverageIgnoreStart
1405 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1406 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1407 $session->remove( 'AuthManager::accountCreationState' );
1408 return $ret;
1409 // @codeCoverageIgnoreEnd
1410 }
1411 $this->setDefaultUserOptions( $user, $creator->isAnon() );
1412 \Hooks::run( 'LocalUserCreated', [ $user, false ] );
1413 $user->saveSettings();
1414 $state['userid'] = $user->getId();
1415
1416 // Update user count
1417 \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1418
1419 // Watch user's userpage and talk page
1420 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1421
1422 // Inform the provider
1423 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1424
1425 // Log the creation
1426 if ( $this->config->get( 'NewUserLog' ) ) {
1427 $isAnon = $creator->isAnon();
1428 $logEntry = new \ManualLogEntry(
1429 'newusers',
1430 $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1431 );
1432 $logEntry->setPerformer( $isAnon ? $user : $creator );
1433 $logEntry->setTarget( $user->getUserPage() );
1436 $state['reqs'], CreationReasonAuthenticationRequest::class
1437 );
1438 $logEntry->setComment( $req ? $req->reason : '' );
1439 $logEntry->setParameters( [
1440 '4::userid' => $user->getId(),
1441 ] );
1442 $logid = $logEntry->insert();
1443 $logEntry->publish( $logid );
1444 }
1445 }
1446
1447 // Step 3: Iterate over all the secondary authentication providers.
1448
1449 $beginReqs = $state['reqs'];
1450
1451 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1452 if ( !isset( $state['secondary'][$id] ) ) {
1453 // This provider isn't started yet, so we pass it the set
1454 // of reqs from beginAuthentication instead of whatever
1455 // might have been used by a previous provider in line.
1456 $func = 'beginSecondaryAccountCreation';
1457 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1458 } elseif ( !$state['secondary'][$id] ) {
1459 $func = 'continueSecondaryAccountCreation';
1460 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1461 } else {
1462 continue;
1463 }
1464 switch ( $res->status ) {
1466 $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1467 'user' => $user->getName(),
1468 'creator' => $creator->getName(),
1469 ] );
1470 // fall through
1472 $state['secondary'][$id] = true;
1473 break;
1476 $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1477 'user' => $user->getName(),
1478 'creator' => $creator->getName(),
1479 ] );
1480 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1481 $state['secondary'][$id] = false;
1482 $state['continueRequests'] = $res->neededRequests;
1483 $session->setSecret( 'AuthManager::accountCreationState', $state );
1484 return $res;
1486 throw new \DomainException(
1487 get_class( $provider ) . "::{$func}() returned $res->status." .
1488 ' Secondary providers are not allowed to fail account creation, that' .
1489 ' should have been done via testForAccountCreation().'
1490 );
1491 // @codeCoverageIgnoreStart
1492 default:
1493 throw new \DomainException(
1494 get_class( $provider ) . "::{$func}() returned $res->status"
1495 );
1496 // @codeCoverageIgnoreEnd
1497 }
1498 }
1499
1500 $id = $user->getId();
1501 $name = $user->getName();
1504 $ret->loginRequest = $req;
1505 $this->createdAccountAuthenticationRequests[] = $req;
1506
1507 $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1508 'user' => $user->getName(),
1509 'creator' => $creator->getName(),
1510 ] );
1511
1512 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1513 $session->remove( 'AuthManager::accountCreationState' );
1514 $this->removeAuthenticationSessionData( null );
1515 return $ret;
1516 } catch ( \Exception $ex ) {
1517 $session->remove( 'AuthManager::accountCreationState' );
1518 throw $ex;
1519 }
1520 }
1521
1537 public function autoCreateUser( User $user, $source, $login = true ) {
1538 if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1540 ) {
1541 throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1542 }
1543
1544 $username = $user->getName();
1545
1546 // Try the local user from the replica DB
1547 $localId = User::idFromName( $username );
1548 $flags = User::READ_NORMAL;
1549
1550 // Fetch the user ID from the master, so that we don't try to create the user
1551 // when they already exist, due to replication lag
1552 // @codeCoverageIgnoreStart
1553 if ( !$localId && wfGetLB()->getReaderIndex() != 0 ) {
1554 $localId = User::idFromName( $username, User::READ_LATEST );
1555 $flags = User::READ_LATEST;
1556 }
1557 // @codeCoverageIgnoreEnd
1558
1559 if ( $localId ) {
1560 $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1561 'username' => $username,
1562 ] );
1563 $user->setId( $localId );
1564 $user->loadFromId( $flags );
1565 if ( $login ) {
1566 $this->setSessionDataForUser( $user );
1567 }
1568 $status = Status::newGood();
1569 $status->warning( 'userexists' );
1570 return $status;
1571 }
1572
1573 // Wiki is read-only?
1574 if ( wfReadOnly() ) {
1575 $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
1576 'username' => $username,
1577 'reason' => wfReadOnlyReason(),
1578 ] );
1579 $user->setId( 0 );
1580 $user->loadFromId();
1581 return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
1582 }
1583
1584 // Check the session, if we tried to create this user already there's
1585 // no point in retrying.
1586 $session = $this->request->getSession();
1587 if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1588 $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1589 'username' => $username,
1590 'sessionid' => $session->getId(),
1591 ] );
1592 $user->setId( 0 );
1593 $user->loadFromId();
1594 $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1595 if ( $reason instanceof StatusValue ) {
1596 return Status::wrap( $reason );
1597 } else {
1598 return Status::newFatal( $reason );
1599 }
1600 }
1601
1602 // Is the username creatable?
1603 if ( !User::isCreatableName( $username ) ) {
1604 $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1605 'username' => $username,
1606 ] );
1607 $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
1608 $user->setId( 0 );
1609 $user->loadFromId();
1610 return Status::newFatal( 'noname' );
1611 }
1612
1613 // Is the IP user able to create accounts?
1614 $anon = new User;
1615 if ( !$anon->isAllowedAny( 'createaccount', 'autocreateaccount' ) ) {
1616 $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1617 'username' => $username,
1618 'ip' => $anon->getName(),
1619 ] );
1620 $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
1621 $session->persist();
1622 $user->setId( 0 );
1623 $user->loadFromId();
1624 return Status::newFatal( 'authmanager-autocreate-noperm' );
1625 }
1626
1627 // Avoid account creation races on double submissions
1628 $cache = \ObjectCache::getLocalClusterInstance();
1629 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1630 if ( !$lock ) {
1631 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1632 'user' => $username,
1633 ] );
1634 $user->setId( 0 );
1635 $user->loadFromId();
1636 return Status::newFatal( 'usernameinprogress' );
1637 }
1638
1639 // Denied by providers?
1640 $options = [
1641 'flags' => User::READ_LATEST,
1642 'creating' => true,
1643 ];
1644 $providers = $this->getPreAuthenticationProviders() +
1647 foreach ( $providers as $provider ) {
1648 $status = $provider->testUserForCreation( $user, $source, $options );
1649 if ( !$status->isGood() ) {
1650 $ret = Status::wrap( $status );
1651 $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1652 'username' => $username,
1653 'reason' => $ret->getWikiText( null, null, 'en' ),
1654 ] );
1655 $session->set( 'AuthManager::AutoCreateBlacklist', $status );
1656 $user->setId( 0 );
1657 $user->loadFromId();
1658 return $ret;
1659 }
1660 }
1661
1662 $backoffKey = wfMemcKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1663 if ( $cache->get( $backoffKey ) ) {
1664 $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1665 'username' => $username,
1666 ] );
1667 $user->setId( 0 );
1668 $user->loadFromId();
1669 return Status::newFatal( 'authmanager-autocreate-exception' );
1670 }
1671
1672 // Checks passed, create the user...
1673 $from = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : 'CLI';
1674 $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1675 'username' => $username,
1676 'from' => $from,
1677 ] );
1678
1679 // Ignore warnings about master connections/writes...hard to avoid here
1680 $trxProfiler = \Profiler::instance()->getTransactionProfiler();
1681 $old = $trxProfiler->setSilenced( true );
1682 try {
1683 $status = $user->addToDatabase();
1684 if ( !$status->isOK() ) {
1685 // Double-check for a race condition (T70012). We make use of the fact that when
1686 // addToDatabase fails due to the user already existing, the user object gets loaded.
1687 if ( $user->getId() ) {
1688 $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1689 'username' => $username,
1690 ] );
1691 if ( $login ) {
1692 $this->setSessionDataForUser( $user );
1693 }
1694 $status = Status::newGood();
1695 $status->warning( 'userexists' );
1696 } else {
1697 $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
1698 'username' => $username,
1699 'msg' => $status->getWikiText( null, null, 'en' )
1700 ] );
1701 $user->setId( 0 );
1702 $user->loadFromId();
1703 }
1704 return $status;
1705 }
1706 } catch ( \Exception $ex ) {
1707 $trxProfiler->setSilenced( $old );
1708 $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1709 'username' => $username,
1710 'exception' => $ex,
1711 ] );
1712 // Do not keep throwing errors for a while
1713 $cache->set( $backoffKey, 1, 600 );
1714 // Bubble up error; which should normally trigger DB rollbacks
1715 throw $ex;
1716 }
1717
1718 $this->setDefaultUserOptions( $user, false );
1719
1720 // Inform the providers
1721 $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1722
1723 \Hooks::run( 'AuthPluginAutoCreate', [ $user ], '1.27' );
1724 \Hooks::run( 'LocalUserCreated', [ $user, true ] );
1725 $user->saveSettings();
1726
1727 // Update user count
1728 \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 0, 0, 0, 1 ) );
1729 // Watch user's userpage and talk page
1730 \DeferredUpdates::addCallableUpdate( function () use ( $user ) {
1731 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1732 } );
1733
1734 // Log the creation
1735 if ( $this->config->get( 'NewUserLog' ) ) {
1736 $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1737 $logEntry->setPerformer( $user );
1738 $logEntry->setTarget( $user->getUserPage() );
1739 $logEntry->setComment( '' );
1740 $logEntry->setParameters( [
1741 '4::userid' => $user->getId(),
1742 ] );
1743 $logEntry->insert();
1744 }
1745
1746 $trxProfiler->setSilenced( $old );
1747
1748 if ( $login ) {
1749 $this->setSessionDataForUser( $user );
1750 }
1751
1752 return Status::newGood();
1753 }
1754
1766 public function canLinkAccounts() {
1767 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1768 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1769 return true;
1770 }
1771 }
1772 return false;
1773 }
1774
1784 public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1785 $session = $this->request->getSession();
1786 $session->remove( 'AuthManager::accountLinkState' );
1787
1788 if ( !$this->canLinkAccounts() ) {
1789 // Caller should have called canLinkAccounts()
1790 throw new \LogicException( 'Account linking is not possible' );
1791 }
1792
1793 if ( $user->getId() === 0 ) {
1794 if ( !User::isUsableName( $user->getName() ) ) {
1795 $msg = wfMessage( 'noname' );
1796 } else {
1797 $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1798 }
1799 return AuthenticationResponse::newFail( $msg );
1800 }
1801 foreach ( $reqs as $req ) {
1802 $req->username = $user->getName();
1803 $req->returnToUrl = $returnToUrl;
1804 }
1805
1806 $this->removeAuthenticationSessionData( null );
1807
1808 $providers = $this->getPreAuthenticationProviders();
1809 foreach ( $providers as $id => $provider ) {
1810 $status = $provider->testForAccountLink( $user );
1811 if ( !$status->isGood() ) {
1812 $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1813 'user' => $user->getName(),
1814 ] );
1816 Status::wrap( $status )->getMessage()
1817 );
1818 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1819 return $ret;
1820 }
1821 }
1822
1823 $state = [
1824 'username' => $user->getName(),
1825 'userid' => $user->getId(),
1826 'returnToUrl' => $returnToUrl,
1827 'primary' => null,
1828 'continueRequests' => [],
1829 ];
1830
1831 $providers = $this->getPrimaryAuthenticationProviders();
1832 foreach ( $providers as $id => $provider ) {
1833 if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1834 continue;
1835 }
1836
1837 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1838 switch ( $res->status ) {
1840 $this->logger->info( "Account linked to {user} by $id", [
1841 'user' => $user->getName(),
1842 ] );
1843 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1844 return $res;
1845
1847 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1848 'user' => $user->getName(),
1849 ] );
1850 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1851 return $res;
1852
1854 // Continue loop
1855 break;
1856
1859 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1860 'user' => $user->getName(),
1861 ] );
1862 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1863 $state['primary'] = $id;
1864 $state['continueRequests'] = $res->neededRequests;
1865 $session->setSecret( 'AuthManager::accountLinkState', $state );
1866 $session->persist();
1867 return $res;
1868
1869 // @codeCoverageIgnoreStart
1870 default:
1871 throw new \DomainException(
1872 get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1873 );
1874 // @codeCoverageIgnoreEnd
1875 }
1876 }
1877
1878 $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1879 'user' => $user->getName(),
1880 ] );
1882 wfMessage( 'authmanager-link-no-primary' )
1883 );
1884 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1885 return $ret;
1886 }
1887
1893 public function continueAccountLink( array $reqs ) {
1894 $session = $this->request->getSession();
1895 try {
1896 if ( !$this->canLinkAccounts() ) {
1897 // Caller should have called canLinkAccounts()
1898 $session->remove( 'AuthManager::accountLinkState' );
1899 throw new \LogicException( 'Account linking is not possible' );
1900 }
1901
1902 $state = $session->getSecret( 'AuthManager::accountLinkState' );
1903 if ( !is_array( $state ) ) {
1905 wfMessage( 'authmanager-link-not-in-progress' )
1906 );
1907 }
1908 $state['continueRequests'] = [];
1909
1910 // Step 0: Prepare and validate the input
1911
1912 $user = User::newFromName( $state['username'], 'usable' );
1913 if ( !is_object( $user ) ) {
1914 $session->remove( 'AuthManager::accountLinkState' );
1915 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1916 }
1917 if ( $user->getId() != $state['userid'] ) {
1918 throw new \UnexpectedValueException(
1919 "User \"{$state['username']}\" is valid, but " .
1920 "ID {$user->getId()} != {$state['userid']}!"
1921 );
1922 }
1923
1924 foreach ( $reqs as $req ) {
1925 $req->username = $state['username'];
1926 $req->returnToUrl = $state['returnToUrl'];
1927 }
1928
1929 // Step 1: Call the primary again until it succeeds
1930
1931 $provider = $this->getAuthenticationProvider( $state['primary'] );
1932 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1933 // Configuration changed? Force them to start over.
1934 // @codeCoverageIgnoreStart
1936 wfMessage( 'authmanager-link-not-in-progress' )
1937 );
1938 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1939 $session->remove( 'AuthManager::accountLinkState' );
1940 return $ret;
1941 // @codeCoverageIgnoreEnd
1942 }
1943 $id = $provider->getUniqueId();
1944 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1945 switch ( $res->status ) {
1947 $this->logger->info( "Account linked to {user} by $id", [
1948 'user' => $user->getName(),
1949 ] );
1950 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1951 $session->remove( 'AuthManager::accountLinkState' );
1952 return $res;
1954 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1955 'user' => $user->getName(),
1956 ] );
1957 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1958 $session->remove( 'AuthManager::accountLinkState' );
1959 return $res;
1962 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1963 'user' => $user->getName(),
1964 ] );
1965 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1966 $state['continueRequests'] = $res->neededRequests;
1967 $session->setSecret( 'AuthManager::accountLinkState', $state );
1968 return $res;
1969 default:
1970 throw new \DomainException(
1971 get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
1972 );
1973 }
1974 } catch ( \Exception $ex ) {
1975 $session->remove( 'AuthManager::accountLinkState' );
1976 throw $ex;
1977 }
1978 }
1979
2005 public function getAuthenticationRequests( $action, User $user = null ) {
2006 $options = [];
2007 $providerAction = $action;
2008
2009 // Figure out which providers to query
2010 switch ( $action ) {
2011 case self::ACTION_LOGIN:
2013 $providers = $this->getPreAuthenticationProviders() +
2016 break;
2017
2019 $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
2020 return is_array( $state ) ? $state['continueRequests'] : [];
2021
2023 $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
2024 return is_array( $state ) ? $state['continueRequests'] : [];
2025
2026 case self::ACTION_LINK:
2027 $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
2028 return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
2029 } );
2030 break;
2031
2033 $providers = array_filter( $this->getPrimaryAuthenticationProviders(), function ( $p ) {
2034 return $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK;
2035 } );
2036
2037 // To providers, unlink and remove are identical.
2038 $providerAction = self::ACTION_REMOVE;
2039 break;
2040
2042 $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
2043 return is_array( $state ) ? $state['continueRequests'] : [];
2044
2047 $providers = $this->getPrimaryAuthenticationProviders() +
2049 break;
2050
2051 // @codeCoverageIgnoreStart
2052 default:
2053 throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2054 }
2055 // @codeCoverageIgnoreEnd
2056
2057 return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2058 }
2059
2070 $providerAction, array $options, array $providers, User $user = null
2071 ) {
2072 $user = $user ?: \RequestContext::getMain()->getUser();
2073 $options['username'] = $user->isAnon() ? null : $user->getName();
2074
2075 // Query them and merge results
2076 $reqs = [];
2077 foreach ( $providers as $provider ) {
2078 $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2079 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2080 $id = $req->getUniqueId();
2081
2082 // If a required request if from a Primary, mark it as "primary-required" instead
2083 if ( $isPrimary ) {
2084 if ( $req->required ) {
2086 }
2087 }
2088
2089 if (
2090 !isset( $reqs[$id] )
2091 || $req->required === AuthenticationRequest::REQUIRED
2092 || $reqs[$id] === AuthenticationRequest::OPTIONAL
2093 ) {
2094 $reqs[$id] = $req;
2095 }
2096 }
2097 }
2098
2099 // AuthManager has its own req for some actions
2100 switch ( $providerAction ) {
2101 case self::ACTION_LOGIN:
2102 $reqs[] = new RememberMeAuthenticationRequest;
2103 break;
2104
2106 $reqs[] = new UsernameAuthenticationRequest;
2107 $reqs[] = new UserDataAuthenticationRequest;
2108 if ( $options['username'] !== null ) {
2110 $options['username'] = null; // Don't fill in the username below
2111 }
2112 break;
2113 }
2114
2115 // Fill in reqs data
2116 $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2117
2118 // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2119 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2120 $reqs = array_filter( $reqs, function ( $req ) {
2121 return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2122 } );
2123 }
2124
2125 return array_values( $reqs );
2126 }
2127
2135 private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2136 foreach ( $reqs as $req ) {
2137 if ( !$req->action || $forceAction ) {
2138 $req->action = $action;
2139 }
2140 if ( $req->username === null ) {
2141 $req->username = $username;
2142 }
2143 }
2144 }
2145
2152 public function userExists( $username, $flags = User::READ_NORMAL ) {
2153 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2154 if ( $provider->testUserExists( $username, $flags ) ) {
2155 return true;
2156 }
2157 }
2158
2159 return false;
2160 }
2161
2173 public function allowsPropertyChange( $property ) {
2174 $providers = $this->getPrimaryAuthenticationProviders() +
2176 foreach ( $providers as $provider ) {
2177 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2178 return false;
2179 }
2180 }
2181 return true;
2182 }
2183
2192 public function getAuthenticationProvider( $id ) {
2193 // Fast version
2194 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2195 return $this->allAuthenticationProviders[$id];
2196 }
2197
2198 // Slow version: instantiate each kind and check
2199 $providers = $this->getPrimaryAuthenticationProviders();
2200 if ( isset( $providers[$id] ) ) {
2201 return $providers[$id];
2202 }
2203 $providers = $this->getSecondaryAuthenticationProviders();
2204 if ( isset( $providers[$id] ) ) {
2205 return $providers[$id];
2206 }
2207 $providers = $this->getPreAuthenticationProviders();
2208 if ( isset( $providers[$id] ) ) {
2209 return $providers[$id];
2210 }
2211
2212 return null;
2213 }
2214
2228 public function setAuthenticationSessionData( $key, $data ) {
2229 $session = $this->request->getSession();
2230 $arr = $session->getSecret( 'authData' );
2231 if ( !is_array( $arr ) ) {
2232 $arr = [];
2233 }
2234 $arr[$key] = $data;
2235 $session->setSecret( 'authData', $arr );
2236 }
2237
2245 public function getAuthenticationSessionData( $key, $default = null ) {
2246 $arr = $this->request->getSession()->getSecret( 'authData' );
2247 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2248 return $arr[$key];
2249 } else {
2250 return $default;
2251 }
2252 }
2253
2259 public function removeAuthenticationSessionData( $key ) {
2260 $session = $this->request->getSession();
2261 if ( $key === null ) {
2262 $session->remove( 'authData' );
2263 } else {
2264 $arr = $session->getSecret( 'authData' );
2265 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2266 unset( $arr[$key] );
2267 $session->setSecret( 'authData', $arr );
2268 }
2269 }
2270 }
2271
2278 protected function providerArrayFromSpecs( $class, array $specs ) {
2279 $i = 0;
2280 foreach ( $specs as &$spec ) {
2281 $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2282 }
2283 unset( $spec );
2284 usort( $specs, function ( $a, $b ) {
2285 return ( (int)$a['sort'] ) - ( (int)$b['sort'] )
2286 ?: $a['sort2'] - $b['sort2'];
2287 } );
2288
2289 $ret = [];
2290 foreach ( $specs as $spec ) {
2291 $provider = \ObjectFactory::getObjectFromSpec( $spec );
2292 if ( !$provider instanceof $class ) {
2293 throw new \RuntimeException(
2294 "Expected instance of $class, got " . get_class( $provider )
2295 );
2296 }
2297 $provider->setLogger( $this->logger );
2298 $provider->setManager( $this );
2299 $provider->setConfig( $this->config );
2300 $id = $provider->getUniqueId();
2301 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2302 throw new \RuntimeException(
2303 "Duplicate specifications for id $id (classes " .
2304 get_class( $provider ) . ' and ' .
2305 get_class( $this->allAuthenticationProviders[$id] ) . ')'
2306 );
2307 }
2308 $this->allAuthenticationProviders[$id] = $provider;
2309 $ret[$id] = $provider;
2310 }
2311 return $ret;
2312 }
2313
2318 private function getConfiguration() {
2319 return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2320 }
2321
2326 protected function getPreAuthenticationProviders() {
2327 if ( $this->preAuthenticationProviders === null ) {
2328 $conf = $this->getConfiguration();
2329 $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2330 PreAuthenticationProvider::class, $conf['preauth']
2331 );
2332 }
2334 }
2335
2341 if ( $this->primaryAuthenticationProviders === null ) {
2342 $conf = $this->getConfiguration();
2343 $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2344 PrimaryAuthenticationProvider::class, $conf['primaryauth']
2345 );
2346 }
2348 }
2349
2355 if ( $this->secondaryAuthenticationProviders === null ) {
2356 $conf = $this->getConfiguration();
2357 $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2358 SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2359 );
2360 }
2362 }
2363
2369 private function setSessionDataForUser( $user, $remember = null ) {
2370 $session = $this->request->getSession();
2371 $delay = $session->delaySave();
2372
2373 $session->resetId();
2374 $session->resetAllTokens();
2375 if ( $session->canSetUser() ) {
2376 $session->setUser( $user );
2377 }
2378 if ( $remember !== null ) {
2379 $session->setRememberUser( $remember );
2380 }
2381 $session->set( 'AuthManager:lastAuthId', $user->getId() );
2382 $session->set( 'AuthManager:lastAuthTimestamp', time() );
2383 $session->persist();
2384
2385 \Wikimedia\ScopedCallback::consume( $delay );
2386
2387 \Hooks::run( 'UserLoggedIn', [ $user ] );
2388 }
2389
2394 private function setDefaultUserOptions( User $user, $useContextLang ) {
2396
2397 $user->setToken();
2398
2399 $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $wgContLang;
2400 $user->setOption( 'language', $lang->getPreferredVariant() );
2401
2402 if ( $wgContLang->hasVariants() ) {
2403 $user->setOption( 'variant', $wgContLang->getPreferredVariant() );
2404 }
2405 }
2406
2412 private function callMethodOnProviders( $which, $method, array $args ) {
2413 $providers = [];
2414 if ( $which & 1 ) {
2415 $providers += $this->getPreAuthenticationProviders();
2416 }
2417 if ( $which & 2 ) {
2418 $providers += $this->getPrimaryAuthenticationProviders();
2419 }
2420 if ( $which & 4 ) {
2421 $providers += $this->getSecondaryAuthenticationProviders();
2422 }
2423 foreach ( $providers as $provider ) {
2424 call_user_func_array( [ $provider, $method ], $args );
2425 }
2426 }
2427
2432 public static function resetCache() {
2433 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2434 // @codeCoverageIgnoreStart
2435 throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2436 // @codeCoverageIgnoreEnd
2437 }
2438
2439 self::$instance = null;
2440 }
2441
2444}
2445
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$wgAuth $wgAuth
Authentication plugin.
wfGetLB( $wiki=false)
Get a load balancer object.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfMemcKey()
Make a cache key for the local wiki.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
if( $line===false) $args
Definition cdb.php:64
const TYPE_RANGE
Definition Block.php:80
Backwards-compatibility wrapper for AuthManager via $wgAuth.
This serves as the entry point to the authentication system.
static resetCache()
Reset the internal caching for unit testing.
canLinkAccounts()
Determine whether accounts can be linked.
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
const SEC_FAIL
Security-sensitive should not be performed.
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...
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
beginAccountCreation(User $creator, array $reqs, $returnToUrl)
Start an account creation flow.
callMethodOnProviders( $which, $method, array $args)
getAuthenticationProvider( $id)
Get a provider by ID.
setSessionDataForUser( $user, $remember=null)
Log the user in.
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
SecondaryAuthenticationProvider[] $secondaryAuthenticationProviders
revokeAccessForUser( $username)
Revoke any authentication credentials for a user.
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
const SEC_REAUTH
Security-sensitive operations should re-authenticate.
getSecondaryAuthenticationProviders()
Get the list of SecondaryAuthenticationProviders.
allowsPropertyChange( $property)
Determine whether a user property should be allowed to be changed.
const ACTION_CREATE_CONTINUE
Continue a user creation process that was interrupted by the need for user input or communication wit...
changeAuthenticationData(AuthenticationRequest $req)
Change authentication data (e.g.
continueAccountLink(array $reqs)
Continue an account linking flow.
setLogger(LoggerInterface $logger)
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
canCreateAccounts()
Determine whether accounts can be created.
canCreateAccount( $username, $options=[])
Determine whether a particular account can be created.
userCanAuthenticate( $username)
Determine whether a username can authenticate.
removeAuthenticationSessionData( $key)
Remove authentication data.
providerArrayFromSpecs( $class, array $specs)
Create an array of AuthenticationProviders from an array of ObjectFactory specs.
forcePrimaryAuthenticationProviders(array $providers, $why)
Force certain PrimaryAuthenticationProviders.
setDefaultUserOptions(User $user, $useContextLang)
checkAccountCreatePermissions(User $creator)
Basic permissions checks on whether a user can create accounts.
static singleton()
Get the global AuthManager.
CreatedAccountAuthenticationRequest[] $createdAccountAuthenticationRequests
AuthenticationProvider[] $allAuthenticationProviders
continueAuthentication(array $reqs)
Continue an authentication flow.
const SEC_OK
Security-sensitive operations are ok.
getAuthenticationRequestsInternal( $providerAction, array $options, array $providers, User $user=null)
Internal request lookup for self::getAuthenticationRequests.
autoCreateUser(User $user, $source, $login=true)
Auto-create an account, and log into that account.
const ACTION_LINK_CONTINUE
Continue a user linking process that was interrupted by the need for user input or communication with...
const ACTION_CHANGE
Change a user's credentials.
canAuthenticateNow()
Indicate whether user authentication is possible.
static callLegacyAuthPlugin( $method, array $params, $return=null)
Call a legacy AuthPlugin method, if necessary.
const ACTION_REMOVE
Remove a user's credentials.
const ACTION_LINK
Link an existing user to a third-party account.
PreAuthenticationProvider[] $preAuthenticationProviders
PrimaryAuthenticationProvider[] $primaryAuthenticationProviders
getConfiguration()
Get the configuration.
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
getAuthenticationRequests( $action, User $user=null)
Return the applicable list of AuthenticationRequests.
fillRequests(array &$reqs, $action, $username, $forceAction=false)
Set values in an array of requests.
userExists( $username, $flags=User::READ_NORMAL)
Determine whether a username exists.
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
__construct(WebRequest $request, Config $config)
normalizeUsername( $username)
Provide normalized versions of the username for security checks.
static AuthManager null $instance
continueAccountCreation(array $reqs)
Continue an account creation flow.
const ACTION_CREATE
Create a new user.
This is a value object for authentication requests.
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
const REQUIRED
Indicates that the request is required for authentication to proceed.
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
const FAIL
Indicates that the authentication failed.
const PASS
Indicates that the authentication succeeded.
const UI
Indicates that the authentication needs further user input of some sort.
const REDIRECT
Indicates that the authentication needs to be redirected to a third party to proceed.
const ABSTAIN
Indicates that the authentication provider does not handle this request.
This transfers state between the login and account creation flows.
Returned from account creation to allow for logging into the created account.
Authentication request for the reason given for account creation.
This is an authentication request added by AuthManager to show a "remember me" checkbox.
This represents additional user data requested on the account creation form.
AuthenticationRequest to ensure something with a username is present.
Class for handling updates to the site_stats table.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:40
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:48
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2108
isDnsBlacklisted( $ip, $checkWhitelist=false)
Whether the given IP is in a DNS blacklist.
Definition User.php:1671
setName( $str)
Set the user name.
Definition User.php:2135
getId()
Get the user's ID.
Definition User.php:2083
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:4111
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
$res
Definition database.txt:21
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:57
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
this hook is for auditing only $req
Definition hooks.txt:1010
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:1049
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
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:1096
either a unescaped string or a HtmlArmor object 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 unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
returning false will NOT prevent logging a wrapping ErrorException 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:2158
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2710
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:1949
this hook is for auditing only or null if authentication failed before getting that far $username
Definition hooks.txt:807
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
$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:37
Interface for configuration instances.
Definition Config.php:28
An AuthenticationProvider is used by AuthManager when authenticating users.
getUniqueId()
Return a unique identifier for this instance.
A pre-authentication provider can prevent authentication early on.
A primary authentication provider is responsible for associating the submitted authentication data wi...
const TYPE_LINK
Provider can link to existing accounts elsewhere.
const TYPE_NONE
Provider cannot create or link to accounts.
A secondary provider mostly acts when the submitted authentication data has already been associated t...
$cache
Definition mcc.php:33
$source
A helper class for throttling authentication attempts.
$last
$property
$params
if(!isset( $args[0])) $lang