MediaWiki master
AuthManager.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Auth;
8
9use InvalidArgumentException;
10use LogicException;
23use MediaWiki\Languages\LanguageConverterFactory;
51use Psr\Log\LoggerAwareInterface;
52use Psr\Log\LoggerInterface;
53use Psr\Log\NullLogger;
54use StatusValue;
55use Wikimedia\NormalizedException\NormalizedException;
56use Wikimedia\ObjectFactory\ObjectFactory;
60
109class AuthManager implements LoggerAwareInterface {
114 public const AUTHN_STATE = 'AuthManager::authnState';
115
120 public const ACCOUNT_CREATION_STATE = 'AuthManager::accountCreationState';
121
126 public const ACCOUNT_LINK_STATE = 'AuthManager::accountLinkState';
127
133 public const AUTOCREATE_BLOCKLIST = 'AuthManager::AutoCreateBlacklist';
134
136 public const ACTION_LOGIN = 'login';
140 public const ACTION_LOGIN_CONTINUE = 'login-continue';
142 public const ACTION_CREATE = 'create';
146 public const ACTION_CREATE_CONTINUE = 'create-continue';
148 public const ACTION_LINK = 'link';
152 public const ACTION_LINK_CONTINUE = 'link-continue';
154 public const ACTION_CHANGE = 'change';
156 public const ACTION_REMOVE = 'remove';
158 public const ACTION_UNLINK = 'unlink';
159
161 public const SEC_OK = 'ok';
163 public const SEC_REAUTH = 'reauth';
165 public const SEC_FAIL = 'fail';
166
168 public const AUTOCREATE_SOURCE_SESSION = SessionManager::class;
169
171 public const AUTOCREATE_SOURCE_MAINT = '::Maintenance::';
172
174 public const AUTOCREATE_SOURCE_TEMP = TempUserCreator::class;
175
180 public const REMEMBER_ME = 'rememberMe';
181
189 public const LOGIN_WAS_INTERACTIVE = 'loginWasInteractive';
190
192 private const CALL_PRE = 1;
193
195 private const CALL_PRIMARY = 2;
196
198 private const CALL_SECONDARY = 4;
199
201 private const CALL_ALL = self::CALL_PRE | self::CALL_PRIMARY | self::CALL_SECONDARY;
202
204 private $allAuthenticationProviders = [];
205
207 private $preAuthenticationProviders = null;
208
210 private $primaryAuthenticationProviders = null;
211
213 private $secondaryAuthenticationProviders = null;
214
216 private $createdAccountAuthenticationRequests = [];
217
218 private LoggerInterface $logger;
219 private LoggerInterface $authEventsLogger;
220 private HookRunner $hookRunner;
221
222 public function __construct(
223 private readonly WebRequest $request,
224 private readonly Config $config,
225 private readonly ChangeTagsStore $changeTagsStore,
226 private readonly ObjectFactory $objectFactory,
227 private readonly ObjectCacheFactory $objectCacheFactory,
228 private readonly HookContainer $hookContainer,
229 private readonly ReadOnlyMode $readOnlyMode,
230 private readonly UserNameUtils $userNameUtils,
231 private readonly BlockManager $blockManager,
232 private readonly WatchlistManager $watchlistManager,
233 private readonly ILoadBalancer $loadBalancer,
234 private readonly Language $contentLanguage,
235 private readonly LanguageConverterFactory $languageConverterFactory,
236 private readonly BotPasswordStore $botPasswordStore,
237 private readonly UserFactory $userFactory,
238 private readonly UserIdentityLookup $userIdentityLookup,
239 private readonly UserIdentityUtils $identityUtils,
240 private readonly UserOptionsManager $userOptionsManager,
241 private readonly NotificationService $notificationService,
242 private readonly SessionManagerInterface $sessionManager,
243 ) {
244 $this->hookRunner = new HookRunner( $hookContainer );
245 $this->setLogger( new NullLogger() );
246 $this->setAuthEventsLogger( new NullLogger() );
247 }
248
249 public function setLogger( LoggerInterface $logger ): void {
250 $this->logger = $logger;
251 }
252
253 public function setAuthEventsLogger( LoggerInterface $authEventsLogger ): void {
254 $this->authEventsLogger = $authEventsLogger;
255 }
256
260 public function getRequest() {
261 return $this->request;
262 }
263
264 /***************************************************************************/
265 // region Authentication
276 public function canAuthenticateNow() {
277 return $this->request->getSession()->canSetUser();
278 }
279
298 public function beginAuthentication( array $reqs, $returnToUrl ) {
299 $session = $this->request->getSession();
300 if ( !$session->canSetUser() ) {
301 // Caller should have called canAuthenticateNow()
302 $session->remove( self::AUTHN_STATE );
303 throw new LogicException( 'Authentication is not possible now' );
304 }
305
306 $guessUserName = null;
307 foreach ( $reqs as $req ) {
308 $req->returnToUrl = $returnToUrl;
309 // @codeCoverageIgnoreStart
310 if ( $req->username !== null && $req->username !== '' ) {
311 if ( $guessUserName === null ) {
312 $guessUserName = $req->username;
313 } elseif ( $guessUserName !== $req->username ) {
314 $guessUserName = null;
315 break;
316 }
317 }
318 // @codeCoverageIgnoreEnd
319 }
320
321 // Check for special-case login of a just-created account
322 $req = AuthenticationRequest::getRequestByClass(
323 $reqs, CreatedAccountAuthenticationRequest::class
324 );
325 if ( $req ) {
326 if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
327 throw new LogicException(
328 'CreatedAccountAuthenticationRequests are only valid on ' .
329 'the same AuthManager that created the account'
330 );
331 }
332
333 $user = $this->userFactory->newFromName( (string)$req->username );
334 // @codeCoverageIgnoreStart
335 if ( !$user ) {
336 throw new \UnexpectedValueException(
337 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
338 );
339 } elseif ( $user->getId() != $req->id ) {
340 throw new \UnexpectedValueException(
341 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
342 );
343 }
344 // @codeCoverageIgnoreEnd
345
346 $this->logger->info( 'Logging in {user} after account creation', [
347 'user' => $user->getName(),
348 ] );
349 $ret = AuthenticationResponse::newPass( $user->getName() );
350 $performer = $session->getUser();
351 $this->setSessionDataForUser( $user );
352 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ $user, $ret ] );
353 $session->remove( self::AUTHN_STATE );
354 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
355 $ret, $user, $user->getName(), [
356 'performer' => $performer
357 ] );
358 return $ret;
359 }
360
361 $this->removeAuthenticationSessionData( null );
362
363 foreach ( $this->getPreAuthenticationProviders() as $provider ) {
364 $status = $provider->testForAuthentication( $reqs );
365 if ( !$status->isGood() ) {
366 $this->logger->debug( 'Login failed in pre-authentication by {providerUniqueId}', [
367 'providerUniqueId' => $provider->getUniqueId(),
368 ] );
369 $ret = AuthenticationResponse::newFail(
370 Status::wrap( $status )->getMessage()
371 );
372 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
373 [ $this->userFactory->newFromName( (string)$guessUserName ), $ret ]
374 );
375 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit( $ret, null, $guessUserName, [
376 'performer' => $session->getUser()
377 ] );
378 return $ret;
379 }
380 }
381
382 $state = [
383 'reqs' => $reqs,
384 'returnToUrl' => $returnToUrl,
385 'guessUserName' => $guessUserName,
386 'providerIds' => $this->getProviderIds(),
387 'primary' => null,
388 'primaryResponse' => null,
389 'secondary' => [],
390 'maybeLink' => [],
391 'continueRequests' => [],
392 ];
393
394 // Preserve state from a previous failed login
395 $req = AuthenticationRequest::getRequestByClass(
396 $reqs, CreateFromLoginAuthenticationRequest::class
397 );
398 if ( $req ) {
399 $state['maybeLink'] = $req->maybeLink;
400 }
401
402 $session = $this->request->getSession();
403 $session->setSecret( self::AUTHN_STATE, $state );
404 $session->persist();
405
406 return $this->continueAuthentication( $reqs );
407 }
408
431 public function continueAuthentication( array $reqs ) {
432 $session = $this->request->getSession();
433 try {
434 if ( !$session->canSetUser() ) {
435 // Caller should have called canAuthenticateNow()
436 // @codeCoverageIgnoreStart
437 throw new LogicException( 'Authentication is not possible now' );
438 // @codeCoverageIgnoreEnd
439 }
440
441 $state = $session->getSecret( self::AUTHN_STATE );
442 if ( !is_array( $state ) ) {
443 return AuthenticationResponse::newFail(
444 wfMessage( 'authmanager-authn-not-in-progress' )
445 );
446 }
447 if ( $state['providerIds'] !== $this->getProviderIds() ) {
448 // An inconsistent AuthManagerFilterProviders hook, or site configuration changed
449 // while the user was in the middle of authentication. The first is a bug, the
450 // second is rare but expected when deploying a config change. Try handle in a way
451 // that's useful for both cases.
452 // @codeCoverageIgnoreStart
453 MWExceptionHandler::logException( new NormalizedException(
454 'Authentication failed because of inconsistent provider array',
455 [ 'old' => json_encode( $state['providerIds'] ), 'new' => json_encode( $this->getProviderIds() ) ]
456 ) );
457 $response = AuthenticationResponse::newFail(
458 wfMessage( 'authmanager-authn-not-in-progress' )
459 );
460 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
461 [ $this->userFactory->newFromName( (string)$state['guessUserName'] ), $response ]
462 );
463 $session->remove( self::AUTHN_STATE );
464 return $response;
465 // @codeCoverageIgnoreEnd
466 }
467 $state['continueRequests'] = [];
468
469 $guessUserName = $state['guessUserName'];
470
471 foreach ( $reqs as $req ) {
472 $req->returnToUrl = $state['returnToUrl'];
473 }
474
475 // Step 1: Choose a primary authentication provider, and call it until it succeeds.
476
477 if ( $state['primary'] === null ) {
478 // We haven't picked a PrimaryAuthenticationProvider yet
479 // @codeCoverageIgnoreStart
480 $guessUserName = null;
481 foreach ( $reqs as $req ) {
482 if ( $req->username !== null && $req->username !== '' ) {
483 if ( $guessUserName === null ) {
484 $guessUserName = $req->username;
485 } elseif ( $guessUserName !== $req->username ) {
486 $guessUserName = null;
487 break;
488 }
489 }
490 }
491 $state['guessUserName'] = $guessUserName;
492 // @codeCoverageIgnoreEnd
493 $state['reqs'] = $reqs;
494
495 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
496 $res = $provider->beginPrimaryAuthentication( $reqs );
497 switch ( $res->status ) {
498 case AuthenticationResponse::PASS:
499 $state['primary'] = $id;
500 $state['primaryResponse'] = $res;
501 $this->logger->debug( 'Primary login with {id} succeeded', [
502 'id' => $id,
503 ] );
504 break 2;
505 case AuthenticationResponse::FAIL:
506 $this->logger->debug( 'Login failed in primary authentication by {id}', [
507 'id' => $id,
508 ] );
509 if ( $res->createRequest || $state['maybeLink'] ) {
510 $res->createRequest = new CreateFromLoginAuthenticationRequest(
511 $res->createRequest, $state['maybeLink']
512 );
513 }
514 $this->callMethodOnProviders(
515 self::CALL_ALL,
516 'postAuthentication',
517 [
518 $this->userFactory->newFromName( (string)$guessUserName ),
519 $res
520 ]
521 );
522 $session->remove( self::AUTHN_STATE );
523 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
524 $res, null, $guessUserName, [
525 'performer' => $session->getUser()
526 ] );
527 return $res;
528 case AuthenticationResponse::ABSTAIN:
529 // Continue loop
530 break;
531 case AuthenticationResponse::REDIRECT:
532 case AuthenticationResponse::UI:
533 $this->logger->debug( 'Primary login with {id} returned {status}', [
534 'id' => $id,
535 'status' => $res->status,
536 ] );
537 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
538 $state['primary'] = $id;
539 $state['continueRequests'] = $res->neededRequests;
540 $session->setSecret( self::AUTHN_STATE, $state );
541 return $res;
542
543 // @codeCoverageIgnoreStart
544 default:
545 throw new \DomainException(
546 get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
547 );
548 // @codeCoverageIgnoreEnd
549 }
550 }
551 if ( $state['primary'] === null ) {
552 $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
553 $response = AuthenticationResponse::newFail(
554 wfMessage( 'authmanager-authn-no-primary' )
555 );
556 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
557 [ $this->userFactory->newFromName( (string)$guessUserName ), $response ]
558 );
559 $session->remove( self::AUTHN_STATE );
560 return $response;
561 }
562 } elseif ( $state['primaryResponse'] === null ) {
563 $provider = $this->getAuthenticationProvider( $state['primary'] );
564 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
565 // Configuration changed? Force them to start over.
566 // @codeCoverageIgnoreStart
567 $response = AuthenticationResponse::newFail(
568 wfMessage( 'authmanager-authn-not-in-progress' )
569 );
570 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
571 [ $this->userFactory->newFromName( (string)$guessUserName ), $response ]
572 );
573 $session->remove( self::AUTHN_STATE );
574 return $response;
575 // @codeCoverageIgnoreEnd
576 }
577 $id = $provider->getUniqueId();
578 $res = $provider->continuePrimaryAuthentication( $reqs );
579 switch ( $res->status ) {
580 case AuthenticationResponse::PASS:
581 $state['primaryResponse'] = $res;
582 $this->logger->debug( 'Primary login with {id} succeeded', [
583 'id' => $id,
584 ] );
585 break;
586 case AuthenticationResponse::FAIL:
587 $this->logger->debug( 'Login failed in primary authentication by {id}', [
588 'id' => $id,
589 ] );
590 if ( $res->createRequest || $state['maybeLink'] ) {
591 $res->createRequest = new CreateFromLoginAuthenticationRequest(
592 $res->createRequest, $state['maybeLink']
593 );
594 }
595 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
596 [ $this->userFactory->newFromName( (string)$guessUserName ), $res ]
597 );
598 $session->remove( self::AUTHN_STATE );
599 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
600 $res, null, $guessUserName, [
601 'performer' => $session->getUser()
602 ] );
603 return $res;
604 case AuthenticationResponse::REDIRECT:
605 case AuthenticationResponse::UI:
606 $this->logger->debug( 'Primary login with {id} returned {status}', [
607 'id' => $id,
608 'status' => $res->status,
609 ] );
610 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
611 $state['continueRequests'] = $res->neededRequests;
612 $session->setSecret( self::AUTHN_STATE, $state );
613 return $res;
614 default:
615 throw new \DomainException(
616 get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
617 );
618 }
619 }
620
621 $res = $state['primaryResponse'];
622 if ( $res->username === null ) {
623 $provider = $this->getAuthenticationProvider( $state['primary'] );
624 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
625 // Configuration changed? Force them to start over.
626 // @codeCoverageIgnoreStart
627 $response = AuthenticationResponse::newFail(
628 wfMessage( 'authmanager-authn-not-in-progress' )
629 );
630 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication',
631 [ $this->userFactory->newFromName( (string)$guessUserName ), $response ]
632 );
633 $session->remove( self::AUTHN_STATE );
634 return $response;
635 // @codeCoverageIgnoreEnd
636 }
637
638 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
639 $res->linkRequest &&
640 // don't confuse the user with an incorrect message if linking is disabled
641 $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
642 ) {
643 $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
644 $msg = 'authmanager-authn-no-local-user-link';
645 } else {
646 $msg = 'authmanager-authn-no-local-user';
647 }
648 $this->logger->debug(
649 'Primary login with {providerUniqueId} succeeded, but returned no user',
650 [ 'providerUniqueId' => $provider->getUniqueId() ]
651 );
652 $response = AuthenticationResponse::newRestart( wfMessage( $msg ) );
653 $response->neededRequests = $this->getAuthenticationRequestsInternal(
654 self::ACTION_LOGIN,
655 [],
656 $this->getPrimaryAuthenticationProviders() + $this->getSecondaryAuthenticationProviders()
657 );
658 if ( $res->createRequest || $state['maybeLink'] ) {
659 $response->createRequest = new CreateFromLoginAuthenticationRequest(
660 $res->createRequest, $state['maybeLink']
661 );
662 $response->neededRequests[] = $response->createRequest;
663 }
664 $this->fillRequests( $response->neededRequests, self::ACTION_LOGIN, null, true );
665 $session->setSecret( self::AUTHN_STATE, [
666 'reqs' => [], // Will be filled in later
667 'primary' => null,
668 'primaryResponse' => null,
669 'secondary' => [],
670 'continueRequests' => $response->neededRequests,
671 ] + $state );
672
673 // Give the AuthManagerVerifyAuthentication hook a chance to interrupt - even though
674 // RESTART does not immediately result in a successful login, the response and session
675 // state can hold information identifying a (remote) user, and that could be turned
676 // into access to that user's account in a follow-up request.
677 if ( !$this->runVerifyHook( self::ACTION_LOGIN, null, $response, $state['primary'] ) ) {
678 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ null, $response ] );
679 $session->remove( self::AUTHN_STATE );
680 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
681 $response, null, null, [ 'performer' => $session->getUser() ]
682 );
683 return $response;
684 }
685
686 return $response;
687 }
688
689 // Step 2: Primary authentication succeeded, create the User object
690 // (and add the user locally if necessary)
691
692 $user = $this->userFactory->newFromName(
693 (string)$res->username,
694 UserRigorOptions::RIGOR_USABLE
695 );
696 if ( !$user ) {
697 $provider = $this->getAuthenticationProvider( $state['primary'] );
698 throw new \DomainException(
699 get_class( $provider ) . " returned an invalid username: {$res->username}"
700 );
701 }
702 if ( !$user->isRegistered() ) {
703 // User doesn't exist locally. Create it.
704 $this->logger->info( 'Auto-creating {user} on login', [
705 'user' => $user->getName(),
706 ] );
707 // Also use $user as performer, because the performer will be used for permission
708 // checks and global rights extensions might add rights based on the username,
709 // even if the user doesn't exist at this point.
710 $status = $this->autoCreateUser( $user, $state['primary'], false, true, $user );
711 if ( !$status->isGood() ) {
712 $response = AuthenticationResponse::newFail(
713 Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
714 );
715 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ $user, $response ] );
716 $session->remove( self::AUTHN_STATE );
717
718 // T390051: Don't use the $user provided to ::autoCreateUser for the "user being authenticated
719 // against" for the user provided in the AuthManagerLoginAuthenticateAudit hook run, as
720 // ::autoCreateUser may reset $user to an anon user.
721 $userForHook = $this->userFactory->newFromName(
722 (string)$res->username, UserRigorOptions::RIGOR_USABLE
723 );
724 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
725 $response, $userForHook, $userForHook->getName(), [
726 'performer' => $session->getUser()
727 ] );
728 return $response;
729 }
730 }
731
732 // Step 3: Iterate over all the secondary authentication providers.
733
734 $beginReqs = $state['reqs'];
735
736 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
737 if ( !isset( $state['secondary'][$id] ) ) {
738 // This provider isn't started yet, so we pass it the set
739 // of reqs from beginAuthentication instead of whatever
740 // might have been used by a previous provider in line.
741 $func = 'beginSecondaryAuthentication';
742 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
743 } elseif ( !$state['secondary'][$id] ) {
744 $func = 'continueSecondaryAuthentication';
745 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
746 } else {
747 continue;
748 }
749 switch ( $res->status ) {
750 case AuthenticationResponse::PASS:
751 $this->logger->debug( 'Secondary login with {id} succeeded', [
752 'id' => $id,
753 ] );
754 // fall through
755 case AuthenticationResponse::ABSTAIN:
756 $state['secondary'][$id] = true;
757 break;
758 case AuthenticationResponse::FAIL:
759 $this->logger->debug( 'Login failed in secondary authentication by {id}', [
760 'id' => $id,
761 ] );
762 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ $user, $res ] );
763 $session->remove( self::AUTHN_STATE );
764 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
765 $res, $user, $user->getName(), [
766 'performer' => $session->getUser()
767 ] );
768 return $res;
769 case AuthenticationResponse::REDIRECT:
770 case AuthenticationResponse::UI:
771 $this->logger->debug( 'Secondary login with {id} returned {status}', [
772 'id' => $id,
773 'status' => $res->status,
774 ] );
775 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
776 $state['secondary'][$id] = false;
777 $state['continueRequests'] = $res->neededRequests;
778 $session->setSecret( self::AUTHN_STATE, $state );
779 return $res;
780
781 // @codeCoverageIgnoreStart
782 default:
783 throw new \DomainException(
784 get_class( $provider ) . "::{$func}() returned $res->status"
785 );
786 // @codeCoverageIgnoreEnd
787 }
788 }
789
790 // Step 4: Authentication complete! Give hook handlers a chance to interrupt, then
791 // set the user in the session and clean up.
792
793 $response = AuthenticationResponse::newPass( $user->getName() );
794 if ( !$this->runVerifyHook( self::ACTION_LOGIN, $user, $response, $state['primary'] ) ) {
795 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ $user, $response ] );
796 $session->remove( self::AUTHN_STATE );
797 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
798 $response, $user, $user->getName(), [
799 'performer' => $session->getUser(),
800 ] );
801 return $response;
802 }
803 $this->logger->info( 'Login for {user} succeeded from {clientIp}',
804 $this->request->getSecurityLogContext( $user ) );
805 $rememberMeConfig = $this->config->get( MainConfigNames::RememberMe );
806 if ( $rememberMeConfig === RememberMeAuthenticationRequest::ALWAYS_REMEMBER ) {
807 $rememberMe = true;
808 } elseif ( $rememberMeConfig === RememberMeAuthenticationRequest::NEVER_REMEMBER ) {
809 $rememberMe = false;
810 } else {
812 $req = AuthenticationRequest::getRequestByClass(
813 $beginReqs, RememberMeAuthenticationRequest::class
814 );
815
816 // T369668: Before we conclude, let's make sure the user hasn't specified
817 // that they want their login remembered elsewhere like in the central domain.
818 // If the user clicked "remember me" in the central domain, then we should
819 // prioritise that when we call continuePrimaryAuthentication() in the provider
820 // that makes calls continuePrimaryAuthentication(). NOTE: It is the responsibility
821 // of the provider to refresh the "remember me" state that will be applied to
822 // the local wiki.
823 $rememberMe = ( $req && $req->rememberMe ) ||
824 $this->getAuthenticationSessionData( self::REMEMBER_ME );
825 }
826 $loginWasInteractive = $this->getAuthenticationSessionData( self::LOGIN_WAS_INTERACTIVE, true );
827 $performer = $session->getUser();
828 // If the session is associated with a temporary account user, invalidate its
829 // session and remove the TempUser:name property from the session
830 // This is necessary in order to ensure that the temporary account session is exited
831 // when the user transitions to a logged-in named account
832 if ( $session->getUser()->isTemp() ) {
833 $this->sessionManager->invalidateSessionsForUser( $session->getUser() );
834 $session->remove( 'TempUser:name' );
835 $performer = new User();
836 }
837 $this->setSessionDataForUser( $user, $rememberMe, $loginWasInteractive );
838 $this->callMethodOnProviders( self::CALL_ALL, 'postAuthentication', [ $user, $response ] );
839 $session->remove( self::AUTHN_STATE );
840 $this->removeAuthenticationSessionData( null );
841 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
842 $response, $user, $user->getName(), [
843 'performer' => $performer
844 ] );
845 return $response;
846 } catch ( \Exception $ex ) {
847 $session->remove( self::AUTHN_STATE );
848 throw $ex;
849 }
850 }
851
863 public function securitySensitiveOperationStatus( $operation ) {
864 $status = self::SEC_OK;
865
866 $this->logger->debug( __METHOD__ . ': Checking {operation}', [
867 'operation' => $operation,
868 ] );
869
870 $session = $this->request->getSession();
871 $aId = $session->getUser()->getId();
872 if ( $aId === 0 ) {
873 // User isn't authenticated. DWIM?
874 $status = $this->canAuthenticateNow() ? self::SEC_REAUTH : self::SEC_FAIL;
875 $this->logger->info( __METHOD__ . ': Not logged in! {operation} is {status}', [
876 'operation' => $operation,
877 'status' => $status,
878 ] );
879 return $status;
880 }
881
882 if ( $session->canSetUser() ) {
883 $id = $session->get( 'AuthManager:lastAuthId' );
884 $last = $session->get( 'AuthManager:lastAuthTimestamp' );
885 if ( $id !== $aId || $last === null ) {
886 $timeSinceLogin = PHP_INT_MAX; // Forever ago
887 } else {
888 $timeSinceLogin = max( 0, time() - $last );
889 }
890
891 $thresholds = $this->config->get( MainConfigNames::ReauthenticateTime );
892 if ( isset( $thresholds[$operation] ) ) {
893 $threshold = $thresholds[$operation];
894 } elseif ( isset( $thresholds['default'] ) ) {
895 $threshold = $thresholds['default'];
896 } else {
897 throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
898 }
899
900 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
901 $status = self::SEC_REAUTH;
902 }
903 } else {
904 $timeSinceLogin = -1;
905
906 $pass = $this->config->get(
907 MainConfigNames::AllowSecuritySensitiveOperationIfCannotReauthenticate );
908 if ( isset( $pass[$operation] ) ) {
909 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
910 } elseif ( isset( $pass['default'] ) ) {
911 $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
912 } else {
913 throw new \UnexpectedValueException(
914 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
915 );
916 }
917 }
918
919 $this->getHookRunner()->onSecuritySensitiveOperationStatus(
920 $status, $operation, $session, $timeSinceLogin );
921
922 // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
923 if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
924 $status = self::SEC_FAIL;
925 }
926
927 $this->logger->info( __METHOD__ . ': {operation} is {status} for {user}',
928 [
929 'operation' => $operation,
930 'status' => $status,
931 ] + $this->getRequest()->getSecurityLogContext( $session->getUser() )
932 );
933
934 return $status;
935 }
936
946 public function userCanAuthenticate( $username ) {
947 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
948 if ( $provider->testUserCanAuthenticate( $username ) ) {
949 return true;
950 }
951 }
952 return false;
953 }
954
969 public function normalizeUsername( $username ) {
970 $ret = [];
971 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
972 $normalized = $provider->providerNormalizeUsername( $username );
973 if ( $normalized !== null ) {
974 $ret[$normalized] = true;
975 }
976 }
977 return array_keys( $ret );
978 }
979
996 public function setRequestContextUserFromSessionUser(): void {
997 $context = RequestContext::getMain();
998 $user = $context->getRequest()->getSession()->getUser();
999
1000 StubGlobalUser::setUser( $user );
1001 $context->setUser( $user );
1002
1003 // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
1004 global $wgLang;
1005 // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
1006 $wgLang = $context->getLanguage();
1007 }
1008
1009 // endregion -- end of Authentication
1010
1011 /***************************************************************************/
1012 // region Authentication data changing
1022 public function revokeAccessForUser( $username ) {
1023 $this->logger->info( 'Revoking access for {user}', [
1024 'user' => $username,
1025 ] );
1026 $this->callMethodOnProviders( self::CALL_PRIMARY | self::CALL_SECONDARY, 'providerRevokeAccessForUser',
1027 [ $username ]
1028 );
1029 }
1030
1040 public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
1041 $any = false;
1042 $providers = $this->getPrimaryAuthenticationProviders() +
1043 $this->getSecondaryAuthenticationProviders();
1044
1045 foreach ( $providers as $provider ) {
1046 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
1047 if ( !$status->isGood() ) {
1048 // If status is not good because reset email password last attempt was within
1049 // $wgPasswordReminderResendTime then return good status with throttled-mailpassword value;
1050 // otherwise, return the $status wrapped.
1051 return $status->hasMessage( 'throttled-mailpassword' )
1052 ? Status::newGood( 'throttled-mailpassword' )
1053 : Status::wrap( $status );
1054 }
1055 $any = $any || $status->value !== 'ignored';
1056 }
1057 if ( !$any ) {
1058 return Status::newGood( 'ignored' )
1059 ->warning( 'authmanager-change-not-supported' );
1060 }
1061 return Status::newGood();
1062 }
1063
1081 public function changeAuthenticationData( AuthenticationRequest $req, $isAddition = false ) {
1082 $this->logger->info( 'Changing authentication data for {user} class {what}', [
1083 'user' => is_string( $req->username ) ? $req->username : '<no name>',
1084 'what' => get_class( $req ),
1085 ] );
1086
1087 $this->callMethodOnProviders( self::CALL_PRIMARY | self::CALL_SECONDARY, 'providerChangeAuthenticationData',
1088 [ $req ]
1089 );
1090
1091 // When the main account's authentication data is changed, invalidate
1092 // all BotPasswords too.
1093 if ( !$isAddition ) {
1094 $this->botPasswordStore->invalidateUserPasswords( (string)$req->username );
1095 }
1096 }
1097
1098 // endregion -- end of Authentication data changing
1099
1100 /***************************************************************************/
1101 // region Account creation
1108 public function canCreateAccounts() {
1109 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1110 switch ( $provider->accountCreationType() ) {
1111 case PrimaryAuthenticationProvider::TYPE_CREATE:
1112 case PrimaryAuthenticationProvider::TYPE_LINK:
1113 return true;
1114 }
1115 }
1116 return false;
1117 }
1118
1127 public function canCreateAccount( $username, $options = [] ) {
1128 // Back compat
1129 if ( is_int( $options ) ) {
1130 $options = [ 'flags' => $options ];
1131 }
1132 $options += [
1133 'flags' => IDBAccessObject::READ_NORMAL,
1134 'creating' => false,
1135 ];
1136 $flags = $options['flags'];
1137
1138 if ( !$this->canCreateAccounts() ) {
1139 return Status::newFatal( 'authmanager-create-disabled' );
1140 }
1141
1142 if ( $this->userExists( $username, $flags ) ) {
1143 return Status::newFatal( 'userexists' );
1144 }
1145
1146 $user = $this->userFactory->newFromName( (string)$username, UserRigorOptions::RIGOR_CREATABLE );
1147 if ( !is_object( $user ) ) {
1148 return Status::newFatal( 'noname' );
1149 } else {
1150 $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
1151 if ( $user->isRegistered() ) {
1152 return Status::newFatal( 'userexists' );
1153 }
1154 }
1155
1156 // Denied by providers?
1157 $providers = $this->getPreAuthenticationProviders() +
1158 $this->getPrimaryAuthenticationProviders() +
1159 $this->getSecondaryAuthenticationProviders();
1160 foreach ( $providers as $provider ) {
1161 $status = $provider->testUserForCreation( $user, false, $options );
1162 if ( !$status->isGood() ) {
1163 return Status::wrap( $status );
1164 }
1165 }
1166
1167 return Status::newGood();
1168 }
1169
1175 private function authorizeInternal(
1176 callable $authorizer,
1177 string $action
1178 ): StatusValue {
1179 // Wiki is read-only?
1180 if ( $this->readOnlyMode->isReadOnly() ) {
1181 return StatusValue::newFatal( wfMessage( 'readonlytext', $this->readOnlyMode->getReason() ) );
1182 }
1183
1184 $permStatus = new PermissionStatus();
1185 if ( !$authorizer(
1186 $action,
1187 SpecialPage::getTitleFor( 'CreateAccount' ),
1188 $permStatus
1189 ) ) {
1190 return $permStatus;
1191 }
1192
1193 $ip = $this->getRequest()->getIP();
1194 if ( $this->blockManager->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
1195 return StatusValue::newFatal( 'sorbs_create_account_reason' );
1196 }
1197
1198 return StatusValue::newGood();
1199 }
1200
1212 public function probablyCanCreateAccount( Authority $creator ): StatusValue {
1213 return $this->authorizeInternal(
1214 static function (
1215 string $action,
1216 PageIdentity $target,
1217 PermissionStatus $status
1218 ) use ( $creator ) {
1219 return $creator->probablyCan( $action, $target, $status );
1220 },
1221 'createaccount'
1222 );
1223 }
1224
1236 public function authorizeCreateAccount( Authority $creator ): StatusValue {
1237 return $this->authorizeInternal(
1238 static function (
1239 string $action,
1240 PageIdentity $target,
1241 PermissionStatus $status
1242 ) use ( $creator ) {
1243 return $creator->authorizeWrite( $action, $target, $status );
1244 },
1245 'createaccount'
1246 );
1247 }
1248
1268 public function beginAccountCreation( Authority $creator, array $reqs, $returnToUrl ) {
1269 $session = $this->request->getSession();
1270 if ( $creator->isTemp() ) {
1271 // For a temp account creating a permanent account, we do not want the temporary
1272 // account to be associated with the created permanent account. To avoid this,
1273 // invalidate their sessions, set the session user to a new anonymous user, save it,
1274 // set the request context from the new session user account. (T393628)
1275 $creatorUser = $this->userFactory->newFromUserIdentity( $creator->getUser() );
1276 $this->sessionManager->invalidateSessionsForUser( $creatorUser );
1277 $creator = $this->userFactory->newAnonymous();
1278 $session->setUser( $creator );
1279 // Ensure the temporary account username is also cleared from the session, this is set
1280 // in TempUserCreator::acquireAndStashName
1281 $session->remove( 'TempUser:name' );
1282 $session->save();
1283 $this->setRequestContextUserFromSessionUser();
1284 }
1285 if ( !$this->canCreateAccounts() ) {
1286 // Caller should have called canCreateAccounts()
1287 $session->remove( self::ACCOUNT_CREATION_STATE );
1288 throw new LogicException( 'Account creation is not possible' );
1289 }
1290
1291 try {
1292 $username = AuthenticationRequest::getUsernameFromRequests( $reqs );
1293 } catch ( \UnexpectedValueException ) {
1294 $username = null;
1295 }
1296 if ( $username === null ) {
1297 $this->logger->debug( __METHOD__ . ': No username provided' );
1298 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1299 }
1300
1301 // Permissions check
1302 $status = Status::wrap( $this->authorizeCreateAccount( $creator ) );
1303 if ( !$status->isGood() ) {
1304 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1305 'user' => $username,
1306 'creator' => $creator->getUser()->getName(),
1307 'reason' => $status->getWikiText( false, false, 'en' )
1308 ] );
1309 return AuthenticationResponse::newFail( $status->getMessage() );
1310 }
1311
1312 // Avoid deadlocks by placing no shared or exclusive gap locks (T199393)
1313 // As defense in-depth, PrimaryAuthenticationProvider::testUserExists only
1314 // supports READ_NORMAL/READ_LATEST (no support for recency query flags).
1315 $status = $this->canCreateAccount(
1316 $username, [ 'flags' => IDBAccessObject::READ_LATEST, 'creating' => true ]
1317 );
1318 if ( !$status->isGood() ) {
1319 $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1320 'user' => $username,
1321 'creator' => $creator->getUser()->getName(),
1322 'reason' => $status->getWikiText( false, false, 'en' )
1323 ] );
1324 return AuthenticationResponse::newFail( $status->getMessage() );
1325 }
1326
1327 $user = $this->userFactory->newFromName( (string)$username, UserRigorOptions::RIGOR_CREATABLE );
1328 foreach ( $reqs as $req ) {
1329 $req->username = $username;
1330 $req->returnToUrl = $returnToUrl;
1331 if ( $req instanceof UserDataAuthenticationRequest ) {
1332 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable user should be checked and valid here
1333 $status = $req->populateUser( $user );
1334 if ( !$status->isGood() ) {
1335 $status = Status::wrap( $status );
1336 $session->remove( self::ACCOUNT_CREATION_STATE );
1337 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1338 'user' => $user->getName(),
1339 'creator' => $creator->getUser()->getName(),
1340 'reason' => $status->getWikiText( false, false, 'en' ),
1341 ] );
1342 return AuthenticationResponse::newFail( $status->getMessage() );
1343 }
1344 }
1345 }
1346
1347 $this->removeAuthenticationSessionData( null );
1348
1349 $state = [
1350 'username' => $username,
1351 'userid' => 0,
1352 'creatorid' => $creator->getUser()->getId(),
1353 'creatorname' => $creator->getUser()->getName(),
1354 'reqs' => $reqs,
1355 'returnToUrl' => $returnToUrl,
1356 'providerIds' => $this->getProviderIds(),
1357 'primary' => null,
1358 'primaryResponse' => null,
1359 'secondary' => [],
1360 'continueRequests' => [],
1361 'maybeLink' => [],
1362 'ranPreTests' => false,
1363 ];
1364
1365 // Special case: converting a login to an account creation
1366 $req = AuthenticationRequest::getRequestByClass(
1367 $reqs, CreateFromLoginAuthenticationRequest::class
1368 );
1369 if ( $req ) {
1370 $state['maybeLink'] = $req->maybeLink;
1371
1372 if ( $req->createRequest ) {
1373 $reqs[] = $req->createRequest;
1374 $state['reqs'][] = $req->createRequest;
1375 }
1376 }
1377
1378 $session->setSecret( self::ACCOUNT_CREATION_STATE, $state );
1379 $session->persist();
1380 $this->logger->debug( __METHOD__ . ': Proceeding with account creation for {username} by {creator}', [
1381 'username' => $user->getName(),
1382 'creator' => $creator->getUser()->getName(),
1383 ] );
1384
1385 return $this->continueAccountCreation( $reqs );
1386 }
1387
1393 public function continueAccountCreation( array $reqs ) {
1394 $session = $this->request->getSession();
1395 try {
1396 if ( !$this->canCreateAccounts() ) {
1397 // Caller should have called canCreateAccounts()
1398 $session->remove( self::ACCOUNT_CREATION_STATE );
1399 throw new LogicException( 'Account creation is not possible' );
1400 }
1401
1402 $state = $session->getSecret( self::ACCOUNT_CREATION_STATE );
1403 if ( !is_array( $state ) ) {
1404 return AuthenticationResponse::newFail(
1405 wfMessage( 'authmanager-create-not-in-progress' )
1406 );
1407 }
1408 $state['continueRequests'] = [];
1409
1410 // Step 0: Prepare and validate the input
1411
1412 $user = $this->userFactory->newFromName(
1413 (string)$state['username'],
1414 UserRigorOptions::RIGOR_CREATABLE
1415 );
1416 if ( !is_object( $user ) ) {
1417 $session->remove( self::ACCOUNT_CREATION_STATE );
1418 $this->logger->debug( __METHOD__ . ': Invalid username', [
1419 'user' => $state['username'],
1420 ] );
1421 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1422 }
1423
1424 if ( $state['creatorid'] ) {
1425 $creator = $this->userFactory->newFromId( (int)$state['creatorid'] );
1426 } else {
1427 $creator = $this->userFactory->newAnonymous();
1428 $creator->setName( $state['creatorname'] );
1429 }
1430
1431 if ( $state['providerIds'] !== $this->getProviderIds() ) {
1432 // An inconsistent AuthManagerFilterProviders hook, or site configuration changed
1433 // while the user was in the middle of authentication. The first is a bug, the
1434 // second is rare but expected when deploying a config change. Try handle in a way
1435 // that's useful for both cases.
1436 // @codeCoverageIgnoreStart
1437 MWExceptionHandler::logException( new NormalizedException(
1438 'Authentication failed because of inconsistent provider array',
1439 [ 'old' => json_encode( $state['providerIds'] ), 'new' => json_encode( $this->getProviderIds() ) ]
1440 ) );
1441 $ret = AuthenticationResponse::newFail(
1442 wfMessage( 'authmanager-create-not-in-progress' )
1443 );
1444 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1445 $session->remove( self::ACCOUNT_CREATION_STATE );
1446 return $ret;
1447 // @codeCoverageIgnoreEnd
1448 }
1449
1450 // Avoid account creation races on double submissions
1451 $cache = $this->objectCacheFactory->getLocalClusterInstance();
1452 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1453 if ( !$lock ) {
1454 // Don't clear AuthManager::accountCreationState for this code
1455 // path because the process that won the race owns it.
1456 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1457 'user' => $user->getName(),
1458 'creator' => $creator->getName(),
1459 ] );
1460 return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1461 }
1462
1463 // Permissions check
1464 $status = Status::wrap( $this->authorizeCreateAccount( $creator ) );
1465 if ( !$status->isGood() ) {
1466 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1467 'user' => $user->getName(),
1468 'creator' => $creator->getName(),
1469 'reason' => $status->getWikiText( false, false, 'en' )
1470 ] );
1471 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1472 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1473 $session->remove( self::ACCOUNT_CREATION_STATE );
1474 return $ret;
1475 }
1476
1477 // Load from primary DB for existence check
1478 $user->load( IDBAccessObject::READ_LATEST );
1479
1480 if ( $state['userid'] === 0 ) {
1481 if ( $user->isRegistered() ) {
1482 $this->logger->debug( __METHOD__ . ': User exists locally', [
1483 'user' => $user->getName(),
1484 'creator' => $creator->getName(),
1485 ] );
1486 $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1487 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1488 $session->remove( self::ACCOUNT_CREATION_STATE );
1489 return $ret;
1490 }
1491 } else {
1492 if ( !$user->isRegistered() ) {
1493 $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1494 'user' => $user->getName(),
1495 'creator' => $creator->getName(),
1496 'expected_id' => $state['userid'],
1497 ] );
1498 throw new \UnexpectedValueException(
1499 "User \"{$state['username']}\" should exist now, but doesn't!"
1500 );
1501 }
1502 if ( $user->getId() !== $state['userid'] ) {
1503 $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1504 'user' => $user->getName(),
1505 'creator' => $creator->getName(),
1506 'expected_id' => $state['userid'],
1507 'actual_id' => $user->getId(),
1508 ] );
1509 throw new \UnexpectedValueException(
1510 "User \"{$state['username']}\" exists, but " .
1511 "ID {$user->getId()} !== {$state['userid']}!"
1512 );
1513 }
1514 }
1515 foreach ( $state['reqs'] as $req ) {
1516 if ( $req instanceof UserDataAuthenticationRequest ) {
1517 $status = $req->populateUser( $user );
1518 if ( !$status->isGood() ) {
1519 // This should never happen...
1520 $status = Status::wrap( $status );
1521 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1522 'user' => $user->getName(),
1523 'creator' => $creator->getName(),
1524 'reason' => $status->getWikiText( false, false, 'en' ),
1525 ] );
1526 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1527 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation',
1528 [ $user, $creator, $ret ]
1529 );
1530 $session->remove( self::ACCOUNT_CREATION_STATE );
1531 return $ret;
1532 }
1533 }
1534 }
1535
1536 foreach ( $reqs as $req ) {
1537 $req->returnToUrl = $state['returnToUrl'];
1538 $req->username = $state['username'];
1539 }
1540
1541 // Run pre-creation tests, if we haven't already
1542 if ( !$state['ranPreTests'] ) {
1543 $providers = $this->getPreAuthenticationProviders() +
1544 $this->getPrimaryAuthenticationProviders() +
1545 $this->getSecondaryAuthenticationProviders();
1546 foreach ( $providers as $id => $provider ) {
1547 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1548 if ( !$status->isGood() ) {
1549 $this->logger->debug( __METHOD__ . ': Fail in pre-authentication by {id}', [
1550 'id' => $id,
1551 'user' => $user->getName(),
1552 'creator' => $creator->getName(),
1553 ] );
1554 $ret = AuthenticationResponse::newFail(
1555 Status::wrap( $status )->getMessage()
1556 );
1557 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation',
1558 [ $user, $creator, $ret ]
1559 );
1560 $session->remove( self::ACCOUNT_CREATION_STATE );
1561 return $ret;
1562 }
1563 }
1564
1565 $state['ranPreTests'] = true;
1566 }
1567
1568 // Step 1: Choose a primary authentication provider and call it until it succeeds.
1569
1570 if ( $state['primary'] === null ) {
1571 // We haven't picked a PrimaryAuthenticationProvider yet
1572 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1573 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1574 continue;
1575 }
1576 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1577 switch ( $res->status ) {
1578 case AuthenticationResponse::PASS:
1579 $this->logger->debug( __METHOD__ . ': Primary creation passed by {id}', [
1580 'id' => $id,
1581 'user' => $user->getName(),
1582 'creator' => $creator->getName(),
1583 ] );
1584 $state['primary'] = $id;
1585 $state['primaryResponse'] = $res;
1586 break 2;
1587 case AuthenticationResponse::FAIL:
1588 $this->logger->debug( __METHOD__ . ': Primary creation failed by {id}', [
1589 'id' => $id,
1590 'user' => $user->getName(),
1591 'creator' => $creator->getName(),
1592 ] );
1593 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation',
1594 [ $user, $creator, $res ]
1595 );
1596 $session->remove( self::ACCOUNT_CREATION_STATE );
1597 return $res;
1598 case AuthenticationResponse::ABSTAIN:
1599 // Continue loop
1600 break;
1601 case AuthenticationResponse::REDIRECT:
1602 case AuthenticationResponse::UI:
1603 $this->logger->debug( __METHOD__ . ': Primary creation {status} by {id}', [
1604 'status' => $res->status,
1605 'id' => $id,
1606 'user' => $user->getName(),
1607 'creator' => $creator->getName(),
1608 ] );
1609 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1610 $state['primary'] = $id;
1611 $state['continueRequests'] = $res->neededRequests;
1612 $session->setSecret( self::ACCOUNT_CREATION_STATE, $state );
1613 return $res;
1614
1615 // @codeCoverageIgnoreStart
1616 default:
1617 throw new \DomainException(
1618 get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1619 );
1620 // @codeCoverageIgnoreEnd
1621 }
1622 }
1623 if ( $state['primary'] === null ) {
1624 $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1625 'user' => $user->getName(),
1626 'creator' => $creator->getName(),
1627 ] );
1628 $ret = AuthenticationResponse::newFail(
1629 wfMessage( 'authmanager-create-no-primary' )
1630 );
1631 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1632 $session->remove( self::ACCOUNT_CREATION_STATE );
1633 return $ret;
1634 }
1635 } elseif ( $state['primaryResponse'] === null ) {
1636 $provider = $this->getAuthenticationProvider( $state['primary'] );
1637 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1638 // Configuration changed? Force them to start over.
1639 // @codeCoverageIgnoreStart
1640 $ret = AuthenticationResponse::newFail(
1641 wfMessage( 'authmanager-create-not-in-progress' )
1642 );
1643 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1644 $session->remove( self::ACCOUNT_CREATION_STATE );
1645 return $ret;
1646 // @codeCoverageIgnoreEnd
1647 }
1648 $id = $provider->getUniqueId();
1649 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1650 switch ( $res->status ) {
1651 case AuthenticationResponse::PASS:
1652 $this->logger->debug( __METHOD__ . ': Primary creation passed by {id}', [
1653 'id' => $id,
1654 'user' => $user->getName(),
1655 'creator' => $creator->getName(),
1656 ] );
1657 $state['primaryResponse'] = $res;
1658 break;
1659 case AuthenticationResponse::FAIL:
1660 $this->logger->debug( __METHOD__ . ': Primary creation failed by {id}', [
1661 'id' => $id,
1662 'user' => $user->getName(),
1663 'creator' => $creator->getName(),
1664 ] );
1665 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation',
1666 [ $user, $creator, $res ]
1667 );
1668 $session->remove( self::ACCOUNT_CREATION_STATE );
1669 return $res;
1670 case AuthenticationResponse::REDIRECT:
1671 case AuthenticationResponse::UI:
1672 $this->logger->debug( __METHOD__ . ': Primary creation {status} by {id}', [
1673 'status' => $res->status,
1674 'id' => $id,
1675 'user' => $user->getName(),
1676 'creator' => $creator->getName(),
1677 ] );
1678 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1679 $state['continueRequests'] = $res->neededRequests;
1680 $session->setSecret( self::ACCOUNT_CREATION_STATE, $state );
1681 return $res;
1682 default:
1683 throw new \DomainException(
1684 get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1685 );
1686 }
1687 }
1688
1689 // Step 2: Primary authentication succeeded. Give hook handlers a chance to interrupt,
1690 // then create the User object and add the user locally.
1691
1692 if ( $state['userid'] === 0 ) {
1693 $response = $state['primaryResponse'];
1694 if ( !$this->runVerifyHook( self::ACTION_CREATE, $user, $response, $state['primary'] ) ) {
1695 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation',
1696 [ $user, $creator, $response ]
1697 );
1698 $session->remove( self::ACCOUNT_CREATION_STATE );
1699 return $response;
1700 }
1701 $this->logger->info( 'Creating user {user} during account creation', [
1702 'user' => $user->getName(),
1703 'creator' => $creator->getName(),
1704 ] );
1705 $status = $user->addToDatabase();
1706 if ( !$status->isOK() ) {
1707 // @codeCoverageIgnoreStart
1708 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1709 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1710 $session->remove( self::ACCOUNT_CREATION_STATE );
1711 return $ret;
1712 // @codeCoverageIgnoreEnd
1713 }
1714 $this->setDefaultUserOptions( $user, $creator->isAnon() );
1715 $this->getHookRunner()->onLocalUserCreated( $user, false );
1716 $this->notificationService->notify(
1717 new WelcomeNotification( $user ),
1718 new RecipientSet( [ $user ] )
1719 );
1720 $user->saveSettings();
1721 $state['userid'] = $user->getId();
1722
1723 // Update user count
1724 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1725
1726 // Watch user's userpage and talk page
1727 $this->watchlistManager->addWatchIgnoringRights( $user, $user->getUserPage() );
1728
1729 // Inform the provider
1730 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable
1731 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1732
1733 // Log the creation
1734 if ( $this->config->get( MainConfigNames::NewUserLog ) ) {
1735 $isNamed = $creator->isNamed();
1736 $logEntry = new ManualLogEntry(
1737 'newusers',
1738 $logSubtype ?: ( $isNamed ? 'create2' : 'create' )
1739 );
1740 $logEntry->setPerformer( $isNamed ? $creator : $user );
1741 $logEntry->setTarget( $user->getUserPage() );
1743 $req = AuthenticationRequest::getRequestByClass(
1744 $state['reqs'], CreationReasonAuthenticationRequest::class
1745 );
1746 $logEntry->setComment( $req ? $req->reason : '' );
1747 $logEntry->setParameters( [
1748 '4::userid' => $user->getId(),
1749 ] );
1750 $logid = $logEntry->insert();
1751 $logEntry->publish( $logid );
1752 }
1753 }
1754
1755 // Step 3: Iterate over all the secondary authentication providers.
1756
1757 $beginReqs = $state['reqs'];
1758
1759 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1760 if ( !isset( $state['secondary'][$id] ) ) {
1761 // This provider isn't started yet, so we pass it the set
1762 // of reqs from beginAuthentication instead of whatever
1763 // might have been used by a previous provider in line.
1764 $func = 'beginSecondaryAccountCreation';
1765 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1766 } elseif ( !$state['secondary'][$id] ) {
1767 $func = 'continueSecondaryAccountCreation';
1768 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1769 } else {
1770 continue;
1771 }
1772 switch ( $res->status ) {
1773 case AuthenticationResponse::PASS:
1774 $this->logger->debug( __METHOD__ . ': Secondary creation passed by {id}', [
1775 'id' => $id,
1776 'user' => $user->getName(),
1777 'creator' => $creator->getName(),
1778 ] );
1779 // fall through
1780 case AuthenticationResponse::ABSTAIN:
1781 $state['secondary'][$id] = true;
1782 break;
1783 case AuthenticationResponse::REDIRECT:
1784 case AuthenticationResponse::UI:
1785 $this->logger->debug( __METHOD__ . ': Secondary creation {status} by {id}', [
1786 'status' => $res->status,
1787 'id' => $id,
1788 'user' => $user->getName(),
1789 'creator' => $creator->getName(),
1790 ] );
1791 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1792 $state['secondary'][$id] = false;
1793 $state['continueRequests'] = $res->neededRequests;
1794 $session->setSecret( self::ACCOUNT_CREATION_STATE, $state );
1795 return $res;
1796 case AuthenticationResponse::FAIL:
1797 throw new \DomainException(
1798 get_class( $provider ) . "::{$func}() returned $res->status." .
1799 ' Secondary providers are not allowed to fail account creation, that' .
1800 ' should have been done via testForAccountCreation().'
1801 );
1802 // @codeCoverageIgnoreStart
1803 default:
1804 throw new \DomainException(
1805 get_class( $provider ) . "::{$func}() returned $res->status"
1806 );
1807 // @codeCoverageIgnoreEnd
1808 }
1809 }
1810
1811 $id = $user->getId();
1812 $name = $user->getName();
1813 $req = new CreatedAccountAuthenticationRequest( $id, $name );
1814 $ret = AuthenticationResponse::newPass( $name );
1815 $ret->loginRequest = $req;
1816 $this->createdAccountAuthenticationRequests[] = $req;
1817
1818 $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1819 'user' => $user->getName(),
1820 'creator' => $creator->getName(),
1821 ] );
1822
1823 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $creator, $ret ] );
1824 $session->remove( self::ACCOUNT_CREATION_STATE );
1825 $this->removeAuthenticationSessionData( null );
1826 return $ret;
1827 } catch ( \Exception $ex ) {
1828 $session->remove( self::ACCOUNT_CREATION_STATE );
1829 throw $ex;
1830 }
1831 }
1832
1840 private function logAutocreationAttempt( Status $status, User $targetUser, $source, $login ) {
1841 if ( $status->isOK() && !$status->isGood() ) {
1842 return; // user already existed, no need to log
1843 }
1844
1845 $firstMessage = $status->getMessages( 'error' )[0] ?? $status->getMessages( 'warning' )[0] ?? null;
1846
1847 $this->authEventsLogger->info( 'Autocreation attempt', [
1848 'event' => 'autocreate',
1849 'successful' => $status->isGood(),
1850 'status' => $firstMessage ? $firstMessage->getKey() : '-',
1851 'accountType' => $this->identityUtils->getShortUserTypeInternal( $targetUser ),
1852 'source' => $source,
1853 'login' => $login,
1854 ] );
1855 }
1856
1867 private function autocreatingTempUserToAppealBlock(
1868 StatusValue $status,
1869 string $source,
1870 User $performer
1871 ): bool {
1872 $block = $status instanceof PermissionStatus ? $status->getBlock() : null;
1873 if ( !( $block instanceof AbstractBlock ) ) {
1874 return false;
1875 }
1876 $title = RequestContext::getMain()->getTitle();
1877 return $title && $title->isSpecial( 'Mytalk' ) &&
1878 $source === self::AUTOCREATE_SOURCE_TEMP &&
1879 $performer->isAnon() &&
1880 count( $status->getErrors() ) === 1 &&
1881 !$block->appliesToUsertalk( $performer->getTalkPage() );
1882 }
1883
1909 public function autoCreateUser(
1910 User $user,
1911 $source,
1912 $login = true,
1913 $log = true,
1914 ?Authority $performer = null,
1915 array $tags = []
1916 ) {
1917 $validSources = [
1918 self::AUTOCREATE_SOURCE_SESSION,
1919 self::AUTOCREATE_SOURCE_MAINT,
1920 self::AUTOCREATE_SOURCE_TEMP
1921 ];
1922 if ( !in_array( $source, $validSources, true )
1923 && !$this->getAuthenticationProvider( $source ) instanceof PrimaryAuthenticationProvider
1924 ) {
1925 throw new InvalidArgumentException( "Unknown auto-creation source: $source" );
1926 }
1927
1928 $username = $user->getName();
1929
1930 // Try the local user from the replica DB, then fall back to the primary.
1931 $localUserIdentity = $this->userIdentityLookup->getUserIdentityByName( $username );
1932 // @codeCoverageIgnoreStart
1933 if ( ( !$localUserIdentity || !$localUserIdentity->isRegistered() )
1934 && $this->loadBalancer->getReaderIndex() !== 0
1935 ) {
1936 $localUserIdentity = $this->userIdentityLookup->getUserIdentityByName(
1937 $username, IDBAccessObject::READ_LATEST
1938 );
1939 }
1940 // @codeCoverageIgnoreEnd
1941 $localId = ( $localUserIdentity && $localUserIdentity->isRegistered() )
1942 ? $localUserIdentity->getId()
1943 : null;
1944
1945 if ( $localId ) {
1946 $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1947 'username' => $username,
1948 ] );
1949 $user->setId( $localId );
1950
1951 // Can't rely on a replica read, not even when getUserIdentityByName() used
1952 // READ_NORMAL, because that method has an in-process cache not shared
1953 // with loadFromId.
1954 $user->loadFromId( IDBAccessObject::READ_LATEST );
1955 if ( $login ) {
1956 $remember = $source === self::AUTOCREATE_SOURCE_TEMP;
1957 $this->setSessionDataForUser( $user, $remember, false );
1958 }
1959 return Status::newGood()->warning( 'userexists' );
1960 }
1961
1962 // Wiki is read-only?
1963 if ( $this->readOnlyMode->isReadOnly() ) {
1964 $reason = $this->readOnlyMode->getReason();
1965 $this->logger->debug( __METHOD__ . ': denied because of read only mode: {reason}', [
1966 'username' => $username,
1967 'reason' => $reason,
1968 ] );
1969 $user->setId( 0 );
1970 $user->loadFromId();
1971 $fatalStatus = Status::newFatal( wfMessage( 'readonlytext', $reason ) );
1972 $this->logAutocreationAttempt( $fatalStatus, $user, $source, $login );
1973 return $fatalStatus;
1974 }
1975
1976 // If there is a non-anonymous performer, don't use their session
1977 $session = null;
1978 $performer ??= $user;
1979 if ( !$performer->isRegistered() || $performer->getUser()->equals( $user ) ) {
1980 // $performer is anonymous, or refers to the same user as $user (i.e., this isn't
1981 // an autocreation attempt via Special:CreateLocalAccount or by the maintenance script)
1982 $session = $this->request->getSession();
1983 }
1984
1985 // Check the session, if we tried to create this user already there's
1986 // no point in retrying.
1987 if ( $session && $session->get( self::AUTOCREATE_BLOCKLIST ) ) {
1988 $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1989 'username' => $username,
1990 'sessionid' => $session->getId(),
1991 ] );
1992 $user->setId( 0 );
1993 $user->loadFromId();
1994 $reason = $session->get( self::AUTOCREATE_BLOCKLIST );
1995
1996 $status = $reason instanceof StatusValue ? Status::wrap( $reason ) : Status::newFatal( $reason );
1997 $this->logAutocreationAttempt( $status, $user, $source, $login );
1998 return $status;
1999 }
2000
2001 // Is the username usable? (Previously isCreatable() was checked here but
2002 // that doesn't work with auto-creation of TempUser accounts by CentralAuth)
2003 if ( !$this->userNameUtils->isUsable( $username ) ) {
2004 $this->logger->debug( __METHOD__ . ': name "{username}" is not usable', [
2005 'username' => $username,
2006 ] );
2007 if ( $session ) {
2008 $session->set( self::AUTOCREATE_BLOCKLIST, 'noname' );
2009 }
2010 $user->setId( 0 );
2011 $user->loadFromId();
2012 $fatalStatus = Status::newFatal( 'noname' );
2013 $this->logAutocreationAttempt( $fatalStatus, $user, $source, $login );
2014 return $fatalStatus;
2015 }
2016
2017 // Is the IP user able to create accounts?
2018 $bypassAuthorization = $session && $session->getProvider()->canAlwaysAutocreate();
2019 if ( $source !== self::AUTOCREATE_SOURCE_MAINT && !$bypassAuthorization ) {
2020 $status = $this->authorizeAutoCreateAccount( $performer );
2021 if ( !$status->isOk() ) {
2022 if ( $this->autocreatingTempUserToAppealBlock( $status, $source, $performer ) ) {
2023 $this->logger->info( __METHOD__ . ': autocreating temporary user to appeal a block', [
2024 'username' => $username,
2025 'creator' => $performer->getUser()->getName(),
2026 ] );
2027 } else {
2028 $this->logger->debug( __METHOD__ . ': cannot create or autocreate accounts', [
2029 'username' => $username,
2030 'creator' => $performer->getUser()->getName(),
2031 ] );
2032 if ( $session ) {
2033 $session->set( self::AUTOCREATE_BLOCKLIST, $status );
2034 $session->persist();
2035 }
2036 $user->setId( 0 );
2037 $user->loadFromId();
2038 $statusWrapped = Status::wrap( $status );
2039 $this->logAutocreationAttempt( $statusWrapped, $user, $source, $login );
2040 return $statusWrapped;
2041 }
2042 }
2043 }
2044
2045 // Avoid account creation races on double submissions
2046 $cache = $this->objectCacheFactory->getLocalClusterInstance();
2047 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
2048 if ( !$lock ) {
2049 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
2050 'user' => $username,
2051 ] );
2052 $user->setId( 0 );
2053 $user->loadFromId();
2054 $status = Status::newFatal( 'usernameinprogress' );
2055 $this->logAutocreationAttempt( $status, $user, $source, $login );
2056 return $status;
2057 }
2058
2059 // Denied by providers?
2060 $options = [
2061 'flags' => IDBAccessObject::READ_LATEST,
2062 'creating' => true,
2063 'canAlwaysAutocreate' => $session && $session->getProvider()->canAlwaysAutocreate(),
2064 'performer' => $performer,
2065 ];
2066 $providers = $this->getPreAuthenticationProviders() +
2067 $this->getPrimaryAuthenticationProviders() +
2068 $this->getSecondaryAuthenticationProviders();
2069 foreach ( $providers as $provider ) {
2070 $status = $provider->testUserForCreation( $user, $source, $options );
2071 if ( !$status->isGood() ) {
2072 $ret = Status::wrap( $status );
2073 $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
2074 'username' => $username,
2075 'reason' => $ret->getWikiText( false, false, 'en' ),
2076 ] );
2077 if ( $session ) {
2078 $session->set( self::AUTOCREATE_BLOCKLIST, $status );
2079 }
2080 $user->setId( 0 );
2081 $user->loadFromId();
2082 $this->logAutocreationAttempt( $ret, $user, $source, $login );
2083 return $ret;
2084 }
2085 }
2086
2087 $backoffKey = $cache->makeKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
2088 if ( $cache->get( $backoffKey ) ) {
2089 $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
2090 'username' => $username,
2091 ] );
2092 $user->setId( 0 );
2093 $user->loadFromId();
2094 $status = Status::newFatal( 'authmanager-autocreate-exception' );
2095 $this->logAutocreationAttempt( $status, $user, $source, $login );
2096 return $status;
2097
2098 }
2099
2100 // Checks passed, create the user...
2101 $from = $_SERVER['REQUEST_URI'] ?? 'CLI';
2102 $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
2103 'username' => $username,
2104 'from' => $from
2105 ] + $this->request->getSecurityLogContext( $performer->getUser() )
2106 );
2107
2108 // Ignore warnings about primary connections/writes...hard to avoid here
2109 $fname = __METHOD__;
2110 $trxLimits = $this->config->get( MainConfigNames::TrxProfilerLimits );
2111 $trxProfiler = Profiler::instance()->getTransactionProfiler();
2112 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
2113 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname ) {
2114 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
2115 } );
2116
2117 try {
2118 $status = $user->addToDatabase();
2119 if ( !$status->isOK() ) {
2120 // Double-check for a race condition (T70012). We make use of the fact that when
2121 // addToDatabase fails due to the user already existing, the user object gets loaded.
2122 if ( $user->getId() ) {
2123 $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
2124 'username' => $username,
2125 ] );
2126 if ( $login ) {
2127 $remember = $source === self::AUTOCREATE_SOURCE_TEMP;
2128 $this->setSessionDataForUser( $user, $remember, false );
2129 }
2130 $status = Status::newGood()->warning( 'userexists' );
2131 } else {
2132 $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
2133 'username' => $username,
2134 'msg' => $status->getWikiText( false, false, 'en' )
2135 ] );
2136 $user->setId( 0 );
2137 $user->loadFromId();
2138 }
2139 $this->logAutocreationAttempt( $status, $user, $source, $login );
2140 return $status;
2141 }
2142 } catch ( \Exception $ex ) {
2143 $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
2144 'username' => $username,
2145 'exception' => $ex,
2146 ] );
2147 // Do not keep throwing errors for a while
2148 $cache->set( $backoffKey, 1, 600 );
2149 // Bubble up error; which should normally trigger DB rollbacks
2150 throw $ex;
2151 }
2152
2153 $this->setDefaultUserOptions( $user, false );
2154
2155 // Inform the providers
2156 $this->callMethodOnProviders( self::CALL_PRIMARY | self::CALL_SECONDARY, 'autoCreatedAccount',
2157 [ $user, $source ]
2158 );
2159
2160 $this->getHookRunner()->onLocalUserCreated( $user, true );
2161 $user->saveSettings();
2162
2163 // Update user count
2164 DeferredUpdates::addUpdate( SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
2165 // Watch user's userpage and talk page (except temp users)
2166 if ( $source !== self::AUTOCREATE_SOURCE_TEMP ) {
2167 DeferredUpdates::addCallableUpdate( function () use ( $user ) {
2168 $this->watchlistManager->addWatchIgnoringRights( $user, $user->getUserPage() );
2169 } );
2170 }
2171
2172 // Log the creation
2173 if ( $this->config->get( MainConfigNames::NewUserLog ) && $log ) {
2174 $logEntry = new ManualLogEntry( 'newusers', 'autocreate' );
2175 $logEntry->setPerformer( $user );
2176 $logEntry->setTarget( $user->getUserPage() );
2177 $logEntry->setComment( '' );
2178 $logEntry->setParameters( [
2179 '4::userid' => $user->getId(),
2180 ] );
2181 $logid = $logEntry->insert();
2182
2183 if ( $tags !== [] ) {
2184 // ManualLogEntry::insert doesn't insert tags
2185 $this->changeTagsStore->addTags( $tags, null, null, $logid );
2186 }
2187 }
2188
2189 if ( $login ) {
2190 $remember = $source === self::AUTOCREATE_SOURCE_TEMP;
2191 $this->setSessionDataForUser( $user, $remember, false );
2192 }
2193 $retStatus = Status::newGood();
2194 $this->logAutocreationAttempt( $retStatus, $user, $source, $login );
2195 return $retStatus;
2196 }
2197
2205 private function authorizeAutoCreateAccount( Authority $creator ) {
2206 return $this->authorizeInternal(
2207 static function (
2208 string $action,
2209 PageIdentity $target,
2210 PermissionStatus $status
2211 ) use ( $creator ) {
2212 return $creator->authorizeWrite( $action, $target, $status );
2213 },
2214 'autocreateaccount'
2215 );
2216 }
2217
2218 // endregion -- end of Account creation
2219
2220 /***************************************************************************/
2221 // region Account linking
2228 public function canLinkAccounts() {
2229 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2230 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2231 return true;
2232 }
2233 }
2234 return false;
2235 }
2236
2246 public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
2247 $session = $this->request->getSession();
2248 $session->remove( self::ACCOUNT_LINK_STATE );
2249
2250 if ( !$this->canLinkAccounts() ) {
2251 // Caller should have called canLinkAccounts()
2252 throw new LogicException( 'Account linking is not possible' );
2253 }
2254
2255 if ( !$user->isRegistered() ) {
2256 if ( !$this->userNameUtils->isUsable( $user->getName() ) ) {
2257 $msg = wfMessage( 'noname' );
2258 } else {
2259 $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
2260 }
2261 return AuthenticationResponse::newFail( $msg );
2262 }
2263 foreach ( $reqs as $req ) {
2264 $req->username = $user->getName();
2265 $req->returnToUrl = $returnToUrl;
2266 }
2267
2268 $this->removeAuthenticationSessionData( null );
2269
2270 $providers = $this->getPreAuthenticationProviders();
2271 foreach ( $providers as $id => $provider ) {
2272 $status = $provider->testForAccountLink( $user );
2273 if ( !$status->isGood() ) {
2274 $this->logger->debug( __METHOD__ . ': Account linking pre-check failed by {id}', [
2275 'id' => $id,
2276 'user' => $user->getName(),
2277 ] );
2278 $ret = AuthenticationResponse::newFail(
2279 Status::wrap( $status )->getMessage()
2280 );
2281 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink', [ $user, $ret ] );
2282 return $ret;
2283 }
2284 }
2285
2286 $state = [
2287 'username' => $user->getName(),
2288 'userid' => $user->getId(),
2289 'returnToUrl' => $returnToUrl,
2290 'providerIds' => $this->getProviderIds(),
2291 'primary' => null,
2292 'continueRequests' => [],
2293 ];
2294
2295 $providers = $this->getPrimaryAuthenticationProviders();
2296 foreach ( $providers as $id => $provider ) {
2297 if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
2298 continue;
2299 }
2300
2301 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
2302 switch ( $res->status ) {
2303 case AuthenticationResponse::PASS:
2304 $this->logger->info( 'Account linked to {user} by {id}', [
2305 'id' => $id,
2306 'user' => $user->getName(),
2307 ] );
2308 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink',
2309 [ $user, $res ]
2310 );
2311 return $res;
2312
2313 case AuthenticationResponse::FAIL:
2314 $this->logger->debug( __METHOD__ . ': Account linking failed by {id}', [
2315 'id' => $id,
2316 'user' => $user->getName(),
2317 ] );
2318 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink',
2319 [ $user, $res ]
2320 );
2321 return $res;
2322
2323 case AuthenticationResponse::ABSTAIN:
2324 // Continue loop
2325 break;
2326
2327 case AuthenticationResponse::REDIRECT:
2328 case AuthenticationResponse::UI:
2329 $this->logger->debug( __METHOD__ . ': Account linking {status} by {id}', [
2330 'status' => $res->status,
2331 'id' => $id,
2332 'user' => $user->getName(),
2333 ] );
2334 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
2335 $state['primary'] = $id;
2336 $state['continueRequests'] = $res->neededRequests;
2337 $session->setSecret( self::ACCOUNT_LINK_STATE, $state );
2338 $session->persist();
2339 return $res;
2340
2341 // @codeCoverageIgnoreStart
2342 default:
2343 throw new \DomainException(
2344 get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
2345 );
2346 // @codeCoverageIgnoreEnd
2347 }
2348 }
2349
2350 $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
2351 'user' => $user->getName(),
2352 ] );
2353 $ret = AuthenticationResponse::newFail(
2354 wfMessage( 'authmanager-link-no-primary' )
2355 );
2356 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink', [ $user, $ret ] );
2357 return $ret;
2358 }
2359
2365 public function continueAccountLink( array $reqs ) {
2366 $session = $this->request->getSession();
2367 try {
2368 if ( !$this->canLinkAccounts() ) {
2369 // Caller should have called canLinkAccounts()
2370 $session->remove( self::ACCOUNT_LINK_STATE );
2371 throw new LogicException( 'Account linking is not possible' );
2372 }
2373
2374 $state = $session->getSecret( self::ACCOUNT_LINK_STATE );
2375 if ( !is_array( $state ) ) {
2376 return AuthenticationResponse::newFail(
2377 wfMessage( 'authmanager-link-not-in-progress' )
2378 );
2379 }
2380 $state['continueRequests'] = [];
2381
2382 // Step 0: Prepare and validate the input
2383
2384 $user = $this->userFactory->newFromName(
2385 (string)$state['username'],
2386 UserRigorOptions::RIGOR_USABLE
2387 );
2388 if ( !is_object( $user ) ) {
2389 $session->remove( self::ACCOUNT_LINK_STATE );
2390 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
2391 }
2392 if ( $user->getId() !== $state['userid'] ) {
2393 throw new \UnexpectedValueException(
2394 "User \"{$state['username']}\" is valid, but " .
2395 "ID {$user->getId()} !== {$state['userid']}!"
2396 );
2397 }
2398
2399 if ( $state['providerIds'] !== $this->getProviderIds() ) {
2400 // An inconsistent AuthManagerFilterProviders hook, or site configuration changed
2401 // while the user was in the middle of authentication. The first is a bug, the
2402 // second is rare but expected when deploying a config change. Try handle in a way
2403 // that's useful for both cases.
2404 // @codeCoverageIgnoreStart
2405 MWExceptionHandler::logException( new NormalizedException(
2406 'Authentication failed because of inconsistent provider array',
2407 [ 'old' => json_encode( $state['providerIds'] ), 'new' => json_encode( $this->getProviderIds() ) ]
2408 ) );
2409 $ret = AuthenticationResponse::newFail(
2410 wfMessage( 'authmanager-link-not-in-progress' )
2411 );
2412 $this->callMethodOnProviders( self::CALL_ALL, 'postAccountCreation', [ $user, $ret ] );
2413 $session->remove( self::ACCOUNT_LINK_STATE );
2414 return $ret;
2415 // @codeCoverageIgnoreEnd
2416 }
2417
2418 foreach ( $reqs as $req ) {
2419 $req->username = $state['username'];
2420 $req->returnToUrl = $state['returnToUrl'];
2421 }
2422
2423 // Step 1: Call the primary again until it succeeds
2424
2425 $provider = $this->getAuthenticationProvider( $state['primary'] );
2426 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
2427 // Configuration changed? Force them to start over.
2428 // @codeCoverageIgnoreStart
2429 $ret = AuthenticationResponse::newFail(
2430 wfMessage( 'authmanager-link-not-in-progress' )
2431 );
2432 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink', [ $user, $ret ] );
2433 $session->remove( self::ACCOUNT_LINK_STATE );
2434 return $ret;
2435 // @codeCoverageIgnoreEnd
2436 }
2437 $id = $provider->getUniqueId();
2438 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
2439 switch ( $res->status ) {
2440 case AuthenticationResponse::PASS:
2441 $this->logger->info( 'Account linked to {user} by {id}', [
2442 'id' => $id,
2443 'user' => $user->getName(),
2444 ] );
2445 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink',
2446 [ $user, $res ]
2447 );
2448 $session->remove( self::ACCOUNT_LINK_STATE );
2449 return $res;
2450 case AuthenticationResponse::FAIL:
2451 $this->logger->debug( __METHOD__ . ': Account linking failed by {id}', [
2452 'id' => $id,
2453 'user' => $user->getName(),
2454 ] );
2455 $this->callMethodOnProviders( self::CALL_PRE | self::CALL_PRIMARY, 'postAccountLink',
2456 [ $user, $res ]
2457 );
2458 $session->remove( self::ACCOUNT_LINK_STATE );
2459 return $res;
2460 case AuthenticationResponse::REDIRECT:
2461 case AuthenticationResponse::UI:
2462 $this->logger->debug( __METHOD__ . ': Account linking {status} by {id}', [
2463 'status' => $res->status,
2464 'id' => $id,
2465 'user' => $user->getName(),
2466 ] );
2467 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
2468 $state['continueRequests'] = $res->neededRequests;
2469 $session->setSecret( self::ACCOUNT_LINK_STATE, $state );
2470 return $res;
2471 default:
2472 throw new \DomainException(
2473 get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
2474 );
2475 }
2476 } catch ( \Exception $ex ) {
2477 $session->remove( self::ACCOUNT_LINK_STATE );
2478 throw $ex;
2479 }
2480 }
2481
2482 // endregion -- end of Account linking
2483
2484 /***************************************************************************/
2485 // region Information methods
2506 public function getAuthenticationRequests( $action, ?UserIdentity $user = null ) {
2507 $options = [];
2508 $providerAction = $action;
2509
2510 // Figure out which providers to query
2511 switch ( $action ) {
2512 case self::ACTION_LOGIN:
2513 case self::ACTION_CREATE:
2514 $providers = $this->getPreAuthenticationProviders() +
2515 $this->getPrimaryAuthenticationProviders() +
2516 $this->getSecondaryAuthenticationProviders();
2517 break;
2518
2519 case self::ACTION_LOGIN_CONTINUE:
2520 $state = $this->request->getSession()->getSecret( self::AUTHN_STATE );
2521 return is_array( $state ) ? $state['continueRequests'] : [];
2522
2523 case self::ACTION_CREATE_CONTINUE:
2524 $state = $this->request->getSession()->getSecret( self::ACCOUNT_CREATION_STATE );
2525 return is_array( $state ) ? $state['continueRequests'] : [];
2526
2527 case self::ACTION_LINK:
2528 $providers = [];
2529 foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2530 if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2531 $providers[] = $p;
2532 }
2533 }
2534 break;
2535
2536 case self::ACTION_UNLINK:
2537 $providers = [];
2538 foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2539 if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2540 $providers[] = $p;
2541 }
2542 }
2543
2544 // To providers, unlink and remove are identical.
2545 $providerAction = self::ACTION_REMOVE;
2546 break;
2547
2548 case self::ACTION_LINK_CONTINUE:
2549 $state = $this->request->getSession()->getSecret( self::ACCOUNT_LINK_STATE );
2550 return is_array( $state ) ? $state['continueRequests'] : [];
2551
2552 case self::ACTION_CHANGE:
2553 case self::ACTION_REMOVE:
2554 $providers = $this->getPrimaryAuthenticationProviders() +
2555 $this->getSecondaryAuthenticationProviders();
2556 break;
2557
2558 // @codeCoverageIgnoreStart
2559 default:
2560 throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2561 }
2562 // @codeCoverageIgnoreEnd
2563
2564 return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2565 }
2566
2576 private function getAuthenticationRequestsInternal(
2577 $providerAction, array $options, array $providers, ?UserIdentity $user = null
2578 ) {
2579 $user = $user ?: RequestContext::getMain()->getUser();
2580 $options['username'] = $user->isRegistered() ? $user->getName() : null;
2581
2582 // Query them and merge results
2583 $reqs = [];
2584 foreach ( $providers as $provider ) {
2585 $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2586 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2587 $id = $req->getUniqueId();
2588
2589 // If a required request if from a Primary, mark it as "primary-required" instead
2590 if ( $isPrimary && $req->required ) {
2591 $req->required = AuthenticationRequest::PRIMARY_REQUIRED;
2592 }
2593
2594 if (
2595 !isset( $reqs[$id] )
2596 || $req->required === AuthenticationRequest::REQUIRED
2597 || $reqs[$id]->required === AuthenticationRequest::OPTIONAL
2598 ) {
2599 $reqs[$id] = $req;
2600 }
2601 }
2602 }
2603
2604 // AuthManager has its own req for some actions
2605 switch ( $providerAction ) {
2606 case self::ACTION_LOGIN:
2607 $reqs[] = new RememberMeAuthenticationRequest(
2608 $this->config->get( MainConfigNames::RememberMe ) );
2609 $options['username'] = null; // Don't fill in the username below
2610 break;
2611
2612 case self::ACTION_CREATE:
2613 $reqs[] = new UsernameAuthenticationRequest;
2614 $reqs[] = new UserDataAuthenticationRequest;
2615
2616 // Registered users should be prompted to provide a rationale for account creations,
2617 // except for the case of a temporary user registering a full account (T328718).
2618 if (
2619 $options['username'] !== null &&
2620 !$this->userNameUtils->isTemp( $options['username'] )
2621 ) {
2622 $reqs[] = new CreationReasonAuthenticationRequest;
2623 $options['username'] = null; // Don't fill in the username below
2624 }
2625 break;
2626 }
2627
2628 // Fill in reqs data
2629 $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2630
2631 // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2632 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2633 $reqs = array_filter( $reqs, function ( $req ) {
2634 return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2635 } );
2636 }
2637
2638 return array_values( $reqs );
2639 }
2640
2648 private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2649 foreach ( $reqs as $req ) {
2650 if ( !$req->action || $forceAction ) {
2651 $req->action = $action;
2652 }
2653 $req->username ??= $username;
2654 }
2655 }
2656
2663 public function userExists( $username, $flags = IDBAccessObject::READ_NORMAL ) {
2664 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2665 if ( $provider->testUserExists( $username, $flags ) ) {
2666 return true;
2667 }
2668 }
2669
2670 return false;
2671 }
2672
2684 public function allowsPropertyChange( $property ) {
2685 $providers = $this->getPrimaryAuthenticationProviders() +
2686 $this->getSecondaryAuthenticationProviders();
2687 foreach ( $providers as $provider ) {
2688 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2689 return false;
2690 }
2691 }
2692 return true;
2693 }
2694
2703 public function getAuthenticationProvider( $id ) {
2704 // Fast version
2705 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2706 return $this->allAuthenticationProviders[$id];
2707 }
2708
2709 // Slow version: instantiate each kind and check
2710 $providers = $this->getPrimaryAuthenticationProviders();
2711 if ( isset( $providers[$id] ) ) {
2712 return $providers[$id];
2713 }
2714 $providers = $this->getSecondaryAuthenticationProviders();
2715 if ( isset( $providers[$id] ) ) {
2716 return $providers[$id];
2717 }
2718 $providers = $this->getPreAuthenticationProviders();
2719 if ( isset( $providers[$id] ) ) {
2720 return $providers[$id];
2721 }
2722
2723 return null;
2724 }
2725
2726 // endregion -- end of Information methods
2727
2728 /***************************************************************************/
2729 // region Internal methods
2738 public function setAuthenticationSessionData( $key, $data ) {
2739 $session = $this->request->getSession();
2740 $arr = $session->getSecret( 'authData' );
2741 if ( !is_array( $arr ) ) {
2742 $arr = [];
2743 }
2744 $arr[$key] = $data;
2745 $session->setSecret( 'authData', $arr );
2746 }
2747
2755 public function getAuthenticationSessionData( $key, $default = null ) {
2756 $arr = $this->request->getSession()->getSecret( 'authData' );
2757 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2758 return $arr[$key];
2759 } else {
2760 return $default;
2761 }
2762 }
2763
2769 public function removeAuthenticationSessionData( $key ) {
2770 $session = $this->request->getSession();
2771 if ( $key === null ) {
2772 $session->remove( 'authData' );
2773 } else {
2774 $arr = $session->getSecret( 'authData' );
2775 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2776 unset( $arr[$key] );
2777 $session->setSecret( 'authData', $arr );
2778 }
2779 }
2780 }
2781
2789 protected function providerArrayFromSpecs( $class, array $specs ) {
2790 $i = 0;
2791 foreach ( $specs as &$spec ) {
2792 $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2793 }
2794 unset( $spec );
2795 // Sort according to the 'sort' field, and if they are equal, according to 'sort2'
2796 usort( $specs, static function ( $a, $b ) {
2797 return $a['sort'] <=> $b['sort']
2798 ?: $a['sort2'] <=> $b['sort2'];
2799 } );
2800
2801 $ret = [];
2802 foreach ( $specs as $spec ) {
2804 $provider = $this->objectFactory->createObject( $spec, [ 'assertClass' => $class ] );
2805 $provider->init( $this->logger, $this, $this->getHookContainer(), $this->config, $this->userNameUtils );
2806 $id = $provider->getUniqueId();
2807 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2808 throw new \RuntimeException(
2809 "Duplicate specifications for id $id (classes " .
2810 get_class( $provider ) . ' and ' .
2811 get_class( $this->allAuthenticationProviders[$id] ) . ')'
2812 );
2813 }
2814 $this->allAuthenticationProviders[$id] = $provider;
2815 $ret[$id] = $provider;
2816 }
2817 return $ret;
2818 }
2819
2824 protected function getPreAuthenticationProviders() {
2825 if ( $this->preAuthenticationProviders === null ) {
2826 $this->initializeAuthenticationProviders();
2827 }
2828 return $this->preAuthenticationProviders;
2829 }
2830
2836 if ( $this->primaryAuthenticationProviders === null ) {
2837 $this->initializeAuthenticationProviders();
2838 }
2839 return $this->primaryAuthenticationProviders;
2840 }
2841
2847 if ( $this->secondaryAuthenticationProviders === null ) {
2848 $this->initializeAuthenticationProviders();
2849 }
2850 return $this->secondaryAuthenticationProviders;
2851 }
2852
2853 private function getProviderIds(): array {
2854 return [
2855 'preauth' => array_keys( $this->getPreAuthenticationProviders() ),
2856 'primaryauth' => array_keys( $this->getPrimaryAuthenticationProviders() ),
2857 'secondaryauth' => array_keys( $this->getSecondaryAuthenticationProviders() ),
2858 ];
2859 }
2860
2861 private function initializeAuthenticationProviders() {
2862 $conf = $this->config->get( MainConfigNames::AuthManagerConfig )
2863 ?: $this->config->get( MainConfigNames::AuthManagerAutoConfig );
2864
2865 $providers = array_map( static fn ( $stepConf ) => array_fill_keys( array_keys( $stepConf ), true ), $conf );
2866 $this->getHookRunner()->onAuthManagerFilterProviders( $providers );
2867 foreach ( $conf as $step => $stepConf ) {
2868 $conf[$step] = array_intersect_key( $stepConf, array_filter( $providers[$step] ) );
2869 }
2870
2871 $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2872 PreAuthenticationProvider::class, $conf['preauth']
2873 );
2874 $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2875 PrimaryAuthenticationProvider::class, $conf['primaryauth']
2876 );
2877 $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2878 SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2879 );
2880 }
2881
2889 private function setSessionDataForUser( $user, $remember = null, $isReauthentication = true ) {
2890 $session = $this->request->getSession();
2891 $delay = $session->delaySave();
2892
2893 $session->resetId();
2894 $session->resetAllTokens();
2895 if ( $session->canSetUser() ) {
2896 $session->setUser( $user );
2897 }
2898 if ( $remember !== null ) {
2899 $session->setRememberUser( $remember );
2900 }
2901 if ( $isReauthentication ) {
2902 $session->set( 'AuthManager:lastAuthId', $user->getId() );
2903 $session->set( 'AuthManager:lastAuthTimestamp', time() );
2904 }
2905 $session->persist();
2906
2907 \Wikimedia\ScopedCallback::consume( $delay );
2908
2909 $this->getHookRunner()->onUserLoggedIn( $user );
2910 }
2911
2916 private function setDefaultUserOptions( User $user, $useContextLang ) {
2917 $user->setToken();
2918
2919 $lang = $useContextLang ? RequestContext::getMain()->getLanguage() : $this->contentLanguage;
2920 $this->userOptionsManager->setOption(
2921 $user,
2922 'language',
2923 $this->languageConverterFactory->getLanguageConverter( $lang )->getPreferredVariant()
2924 );
2925
2926 $contLangConverter = $this->languageConverterFactory->getLanguageConverter( $this->contentLanguage );
2927 if ( $contLangConverter->hasVariants() ) {
2928 $this->userOptionsManager->setOption(
2929 $user,
2930 'variant',
2931 $contLangConverter->getPreferredVariant()
2932 );
2933 }
2934 }
2935
2939 private function runVerifyHook(
2940 string $action,
2941 ?UserIdentity $user,
2942 AuthenticationResponse &$response,
2943 string $primaryId
2944 ): bool {
2945 $oldResponse = $response;
2946 $info = [
2947 'action' => $action,
2948 'primaryId' => $primaryId,
2949 ];
2950 $proceed = $this->getHookRunner()->onAuthManagerVerifyAuthentication( $user, $response, $this, $info );
2951 if ( !( $response instanceof AuthenticationResponse ) ) {
2952 throw new LogicException( '$response must be an AuthenticationResponse' );
2953 } elseif ( $proceed && $response !== $oldResponse ) {
2954 throw new LogicException(
2955 'AuthManagerVerifyAuthenticationHook must not modify the response unless it returns false' );
2956 } elseif ( !$proceed && $response->status !== AuthenticationResponse::FAIL ) {
2957 throw new LogicException(
2958 'AuthManagerVerifyAuthenticationHook must set the response to FAIL if it returns false' );
2959 }
2960 if ( !$proceed ) {
2961 $this->logger->info(
2962 $action . ' action for {user} from {clientIp} prevented by '
2963 . 'AuthManagerVerifyAuthentication hook: {reason}',
2964 [
2965 'user' => $user ? $user->getName() : '<null>',
2966 'reason' => $response->message->getKey(),
2967 'primaryId' => $primaryId,
2968 ] + $this->request->getSecurityLogContext( $user )
2969 );
2970 }
2971 return $proceed;
2972 }
2973
2979 private function callMethodOnProviders( $which, $method, array $args ) {
2980 $providers = [];
2981 if ( $which & self::CALL_PRE ) {
2982 $providers += $this->getPreAuthenticationProviders();
2983 }
2984 if ( $which & self::CALL_PRIMARY ) {
2985 $providers += $this->getPrimaryAuthenticationProviders();
2986 }
2987 if ( $which & self::CALL_SECONDARY ) {
2988 $providers += $this->getSecondaryAuthenticationProviders();
2989 }
2990 foreach ( $providers as $provider ) {
2991 $provider->$method( ...$args );
2992 }
2993 }
2994
2998 private function getHookContainer() {
2999 return $this->hookContainer;
3000 }
3001
3005 private function getHookRunner() {
3006 return $this->hookRunner;
3007 }
3008
3009 // endregion -- end of Internal methods
3010
3011}
3012
3013/*
3014 * This file uses VisualStudio style region/endregion fold markers which are
3015 * recognised by PHPStorm. If modelines are enabled, the following editor
3016 * configuration will also enable folding in vim, if it is in the last 5 lines
3017 * of the file. We also use "@name" which creates sections in Doxygen.
3018 *
3019 * vim: foldmarker=//\ region,//\ endregion foldmethod=marker
3020 */
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(MW_ENTRY_POINT==='index') if(!defined( 'MW_NO_SESSION') &&MW_ENTRY_POINT !=='cli' $wgLang
Definition Setup.php:551
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
AuthManager is the authentication system in MediaWiki and serves entry point for authentication.
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...
autoCreateUser(User $user, $source, $login=true, $log=true, ?Authority $performer=null, array $tags=[])
Auto-create an account and optionally log into that account.
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
getAuthenticationProvider( $id)
Get a provider by ID.
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
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...
const AUTOCREATE_SOURCE_TEMP
Auto-creation is due to temporary account creation on page save.
continueAccountLink(array $reqs)
Continue an account linking flow.
setLogger(LoggerInterface $logger)
userExists( $username, $flags=IDBAccessObject::READ_NORMAL)
Determine whether a username exists.
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
probablyCanCreateAccount(Authority $creator)
Check whether $creator can create accounts.
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
beginAccountCreation(Authority $creator, array $reqs, $returnToUrl)
Start an account creation flow.
setAuthEventsLogger(LoggerInterface $authEventsLogger)
canCreateAccounts()
Determine whether accounts can be created.
canCreateAccount( $username, $options=[])
Determine whether a particular account can be created.
changeAuthenticationData(AuthenticationRequest $req, $isAddition=false)
Change authentication data (e.g.
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 @template T of Authen...
const AUTOCREATE_SOURCE_MAINT
Auto-creation is due to a Maintenance script.
continueAuthentication(array $reqs)
Continue an authentication flow.
const SEC_OK
Security-sensitive operations are ok.
setRequestContextUserFromSessionUser()
Call this method to set the request context user for the current request from the context session use...
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.
const ACTION_REMOVE
Remove a user's credentials.
getAuthenticationRequests( $action, ?UserIdentity $user=null)
Return the applicable list of AuthenticationRequests.
const ACTION_LINK
Link an existing user to a third-party account.
authorizeCreateAccount(Authority $creator)
Authorize the account creation by $creator.
__construct(private readonly WebRequest $request, private readonly Config $config, private readonly ChangeTagsStore $changeTagsStore, private readonly ObjectFactory $objectFactory, private readonly ObjectCacheFactory $objectCacheFactory, private readonly HookContainer $hookContainer, private readonly ReadOnlyMode $readOnlyMode, private readonly UserNameUtils $userNameUtils, private readonly BlockManager $blockManager, private readonly WatchlistManager $watchlistManager, private readonly ILoadBalancer $loadBalancer, private readonly Language $contentLanguage, private readonly LanguageConverterFactory $languageConverterFactory, private readonly BotPasswordStore $botPasswordStore, private readonly UserFactory $userFactory, private readonly UserIdentityLookup $userIdentityLookup, private readonly UserIdentityUtils $identityUtils, private readonly UserOptionsManager $userOptionsManager, private readonly NotificationService $notificationService, private readonly SessionManagerInterface $sessionManager,)
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
normalizeUsername( $username)
Provide normalized versions of the username for security checks.
continueAccountCreation(array $reqs)
Continue an account creation flow.
const ACTION_CREATE
Create a new user.
This is a value object for authentication requests.
This transfers state between the login and account creation flows.
Returned from account creation to allow for logging into the created account.
This represents additional user data requested on the account creation form.
A service class for checking blocks.
Read-write access to the change_tags table.
Group all the pieces relevant to the context of a request into one instance.
Defer callable updates to run later in the PHP process.
Class for handling updates to the site_stats table.
Handler class for MWExceptions.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Base class for language-specific code.
Definition Language.php:68
Class for creating new log entries and inserting them into the database.
A class containing constants representing the names of configuration variables.
Notify users about things occurring.
Factory for cache objects as configured in the ObjectCaches setting.
A StatusValue for permission errors.
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:25
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
This serves as the entry point to the MediaWiki session handling system.
Parent class for all special pages.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Stub object for the global user ($wgUser) that makes it possible to change the relevant underlying ob...
BotPassword interaction with databases.
A service class to control user options.
Service for temporary user creation.
Create User objects.
Convenience functions for interpreting UserIdentity objects using additional services or config.
UserNameUtils service.
User class for the MediaWiki software.
Definition User.php:130
getId( $wikiId=self::LOCAL)
Get the user's ID.
Definition User.php:1491
getUserPage()
Get this user's personal page title.
Definition User.php:2710
addToDatabase()
Add this existing user object to the database.
Definition User.php:2541
loadFromId( $flags=IDBAccessObject::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:484
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:1515
saveSettings()
Save this user's settings into the database.
Definition User.php:2335
isRegistered()
Get whether the user is registered.
Definition User.php:2091
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:1524
Generic operation result class Has warning/error list, boolean status and arbitrary value.
getErrors()
Get the list of errors.
getMessages(?string $type=null)
Returns a list of error messages, optionally only those of the given type.
hasMessage(string $message)
Returns true if the specified message is present as a warning or error.
isOK()
Returns whether the operation completed.
error( $message,... $parameters)
Add an error, do not set fatal flag This can be used for non-fatal errors.
warning( $message,... $parameters)
Add a new warning.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
static newGood( $value=null)
Factory function for good results.
Determine whether a site is currently in read-only mode.
return[ 'config-schema-inverse'=>['default'=>['ConfigRegistry'=>['main'=> 'MediaWiki\\Config\\GlobalVarConfig::newInstance',], 'Sitename'=> 'MediaWiki', 'Server'=> false, 'CanonicalServer'=> false, 'ServerName'=> false, 'AssumeProxiesUseDefaultProtocolPorts'=> true, 'HttpsPort'=> 443, 'ForceHTTPS'=> false, 'ScriptPath'=> '/wiki', 'UsePathInfo'=> null, 'Script'=> false, 'LoadScript'=> false, 'RestPath'=> false, 'StylePath'=> false, 'LocalStylePath'=> false, 'ExtensionAssetsPath'=> false, 'ExtensionDirectory'=> null, 'StyleDirectory'=> null, 'ArticlePath'=> false, 'UploadPath'=> false, 'ImgAuthPath'=> false, 'ThumbPath'=> false, 'UploadDirectory'=> false, 'FileCacheDirectory'=> false, 'Logo'=> false, 'Logos'=> false, 'Favicon'=> '/favicon.ico', 'AppleTouchIcon'=> false, 'ReferrerPolicy'=> false, 'TmpDirectory'=> false, 'UploadBaseUrl'=> '', 'UploadStashScalerBaseUrl'=> false, 'ActionPaths'=>[], 'MainPageIsDomainRoot'=> false, 'EnableUploads'=> false, 'UploadStashMaxAge'=> 21600, 'EnableAsyncUploads'=> false, 'EnableAsyncUploadsByURL'=> false, 'UploadMaintenance'=> false, 'IllegalFileChars'=> ':\\/\\\\', 'DeletedDirectory'=> false, 'ImgAuthDetails'=> false, 'ImgAuthUrlPathMap'=>[], 'LocalFileRepo'=>['class'=> 'MediaWiki\\FileRepo\\LocalRepo', 'name'=> 'local', 'directory'=> null, 'scriptDirUrl'=> null, 'favicon'=> null, 'url'=> null, 'hashLevels'=> null, 'thumbScriptUrl'=> null, 'transformVia404'=> null, 'deletedDir'=> null, 'deletedHashLevels'=> null, 'updateCompatibleMetadata'=> null, 'reserializeMetadata'=> null,], 'ForeignFileRepos'=>[], 'UseInstantCommons'=> false, 'UseSharedUploads'=> false, 'SharedUploadDirectory'=> null, 'SharedUploadPath'=> null, 'HashedSharedUploadDirectory'=> true, 'RepositoryBaseUrl'=> 'https:'FetchCommonsDescriptions'=> false, 'SharedUploadDBname'=> false, 'SharedUploadDBprefix'=> '', 'CacheSharedUploads'=> true, 'ForeignUploadTargets'=>['local',], 'UploadDialog'=>['fields'=>['description'=> true, 'date'=> false, 'categories'=> false,], 'licensemessages'=>['local'=> 'generic-local', 'foreign'=> 'generic-foreign',], 'comment'=>['local'=> '', 'foreign'=> '',], 'format'=>['filepage'=> ' $DESCRIPTION', 'description'=> ' $TEXT', 'ownwork'=> '', 'license'=> '', 'uncategorized'=> '',],], 'FileBackends'=>[], 'LockManagers'=>[], 'ShowEXIF'=> null, 'UpdateCompatibleMetadata'=> false, 'AllowCopyUploads'=> false, 'CopyUploadsDomains'=>[], 'CopyUploadsFromSpecialUpload'=> false, 'CopyUploadProxy'=> false, 'CopyUploadTimeout'=> false, 'CopyUploadAllowOnWikiDomainConfig'=> false, 'MaxUploadSize'=> 104857600, 'MinUploadChunkSize'=> 1024, 'UploadNavigationUrl'=> false, 'UploadMissingFileUrl'=> false, 'ThumbnailScriptPath'=> false, 'SharedThumbnailScriptPath'=> false, 'HashedUploadDirectory'=> true, 'CSPUploadEntryPoint'=> true, 'FileExtensions'=>['png', 'gif', 'jpg', 'jpeg', 'webp',], 'ProhibitedFileExtensions'=>['html', 'htm', 'js', 'jsb', 'mhtml', 'mht', 'xhtml', 'xht', 'php', 'phtml', 'php3', 'php4', 'php5', 'phps', 'phar', 'shtml', 'jhtml', 'pl', 'py', 'cgi', 'exe', 'scr', 'dll', 'msi', 'vbs', 'bat', 'com', 'pif', 'cmd', 'vxd', 'cpl', 'xml',], 'MimeTypeExclusions'=>['text/html', 'application/javascript', 'text/javascript', 'text/x-javascript', 'application/x-shellscript', 'application/x-php', 'text/x-php', 'text/x-python', 'text/x-perl', 'text/x-bash', 'text/x-sh', 'text/x-csh', 'text/scriptlet', 'application/x-msdownload', 'application/x-msmetafile', 'application/java', 'application/xml', 'text/xml',], 'CheckFileExtensions'=> true, 'StrictFileExtensions'=> true, 'DisableUploadScriptChecks'=> false, 'UploadSizeWarning'=> false, 'TrustedMediaFormats'=>['BITMAP', 'AUDIO', 'VIDEO', 'image/svg+xml', 'application/pdf',], 'MediaHandlers'=>[], 'NativeImageLazyLoading'=> false, 'ParserTestMediaHandlers'=>['image/jpeg'=> 'MockBitmapHandler', 'image/png'=> 'MockBitmapHandler', 'image/gif'=> 'MockBitmapHandler', 'image/tiff'=> 'MockBitmapHandler', 'image/webp'=> 'MockBitmapHandler', 'image/x-ms-bmp'=> 'MockBitmapHandler', 'image/x-bmp'=> 'MockBitmapHandler', 'image/x-xcf'=> 'MockBitmapHandler', 'image/svg+xml'=> 'MockSvgHandler', 'image/vnd.djvu'=> 'MockDjVuHandler',], 'UseImageResize'=> true, 'UseImageMagick'=> false, 'ImageMagickConvertCommand'=> '/usr/bin/convert', 'MaxInterlacingAreas'=>[], 'SharpenParameter'=> '0x0.4', 'SharpenReductionThreshold'=> 0.85, 'ImageMagickTempDir'=> false, 'CustomConvertCommand'=> false, 'JpegTran'=> '/usr/bin/jpegtran', 'JpegPixelFormat'=> 'yuv420', 'JpegQuality'=> 80, 'Exiv2Command'=> '/usr/bin/exiv2', 'Exiftool'=> '/usr/bin/exiftool', 'SVGConverters'=>['ImageMagick'=> ' $path/convert -background "#ffffff00" -thumbnail $widthx$height\\! $input PNG:$output', 'inkscape'=> ' $path/inkscape -w $width -o $output $input', 'batik'=> 'java -Djava.awt.headless=true -jar $path/batik-rasterizer.jar -w $width -d $output $input', 'rsvg'=> ' $path/rsvg-convert -w $width -h $height -o $output $input', 'ImagickExt'=>['SvgHandler::rasterizeImagickExt',],], 'SVGConverter'=> 'ImageMagick', 'SVGConverterPath'=> '', 'SVGMaxSize'=> 5120, 'SVGMetadataCutoff'=> 5242880, 'SVGNativeRendering'=> true, 'SVGNativeRenderingSizeLimit'=> 51200, 'MediaInTargetLanguage'=> true, 'MaxImageArea'=> 12500000, 'MaxAnimatedGifArea'=> 12500000, 'TiffThumbnailType'=>[], 'ThumbnailEpoch'=> '20030516000000', 'AttemptFailureEpoch'=> 1, 'IgnoreImageErrors'=> false, 'GenerateThumbnailOnParse'=> true, 'ShowArchiveThumbnails'=> true, 'EnableAutoRotation'=> null, 'Antivirus'=> null, 'AntivirusSetup'=>['clamav'=>['command'=> 'clamscan --no-summary ', 'codemap'=>[0=> 0, 1=> 1, 52=> -1, ' *'=> false,], 'messagepattern'=> '/.*?:(.*)/sim',],], 'AntivirusRequired'=> true, 'VerifyMimeType'=> true, 'MimeTypeFile'=> 'internal', 'MimeInfoFile'=> 'internal', 'MimeDetectorCommand'=> null, 'TrivialMimeDetection'=> false, 'XMLMimeTypes'=>['http:'svg'=> 'image/svg+xml', 'http:'http:'html'=> 'text/html',], 'ImageLimits'=>[[320, 240,], [640, 480,], [800, 600,], [1024, 768,], [1280, 1024,], [2560, 2048,],], 'ThumbLimits'=>[120, 150, 180, 200, 250, 300,], 'ThumbnailNamespaces'=>[6,], 'ThumbnailSteps'=> null, 'ThumbnailStepsRatio'=> null, 'ThumbnailBuckets'=> null, 'ThumbnailMinimumBucketDistance'=> 50, 'UploadThumbnailRenderMap'=>[], 'UploadThumbnailRenderMethod'=> 'jobqueue', 'UploadThumbnailRenderHttpCustomHost'=> false, 'UploadThumbnailRenderHttpCustomDomain'=> false, 'UseTinyRGBForJPGThumbnails'=> false, 'GalleryOptions'=>[], 'ThumbUpright'=> 0.75, 'DirectoryMode'=> 511, 'ResponsiveImages'=> true, 'ImagePreconnect'=> false, 'DjvuUseBoxedCommand'=> false, 'DjvuDump'=> null, 'DjvuRenderer'=> null, 'DjvuTxt'=> null, 'DjvuPostProcessor'=> 'pnmtojpeg', 'DjvuOutputExtension'=> 'jpg', 'EmergencyContact'=> false, 'PasswordSender'=> false, 'NoReplyAddress'=> false, 'EnableEmail'=> true, 'EnableUserEmail'=> true, 'UserEmailUseReplyTo'=> true, 'PasswordReminderResendTime'=> 24, 'NewPasswordExpiry'=> 604800, 'UserEmailConfirmationTokenExpiry'=> 604800, 'UserEmailConfirmationUseHTML'=> false, 'PasswordExpirationDays'=> false, 'PasswordExpireGrace'=> 604800, 'SMTP'=> false, 'AdditionalMailParams'=> null, 'AllowHTMLEmail'=> false, 'EnotifFromEditor'=> false, 'EmailAuthentication'=> true, 'EnotifWatchlist'=> false, 'EnotifUserTalk'=> false, 'EnotifRevealEditorAddress'=> false, 'EnotifMinorEdits'=> true, 'EnotifUseRealName'=> false, 'UsersNotifiedOnAllChanges'=>[], 'DBname'=> 'my_wiki', 'DBmwschema'=> null, 'DBprefix'=> '', 'DBserver'=> 'localhost', 'DBport'=> 5432, 'DBuser'=> 'wikiuser', 'DBpassword'=> '', 'DBtype'=> 'mysql', 'DBssl'=> false, 'DBcompress'=> false, 'DBStrictWarnings'=> false, 'DBadminuser'=> null, 'DBadminpassword'=> null, 'SearchType'=> null, 'SearchTypeAlternatives'=> null, 'DBTableOptions'=> 'ENGINE=InnoDB, DEFAULT CHARSET=binary', 'SQLMode'=> '', 'SQLiteDataDir'=> '', 'SharedDB'=> null, 'SharedPrefix'=> false, 'SharedTables'=>['user', 'user_properties', 'user_autocreate_serial',], 'SharedSchema'=> false, 'DBservers'=> false, 'LBFactoryConf'=>['class'=> 'Wikimedia\\Rdbms\\LBFactorySimple',], 'DataCenterUpdateStickTTL'=> 10, 'DBerrorLog'=> false, 'DBerrorLogTZ'=> false, 'LocalDatabases'=>[], 'DatabaseReplicaLagWarning'=> 10, 'DatabaseReplicaLagCritical'=> 30, 'MaxExecutionTimeForExpensiveQueries'=> 0, 'VirtualDomainsMapping'=>[], 'FileSchemaMigrationStage'=> 3, 'ImageLinksSchemaMigrationStage'=> 769, 'ExternalLinksDomainGaps'=>[], 'ContentHandlers'=>['wikitext'=>['class'=> 'MediaWiki\\Content\\WikitextContentHandler', 'services'=>['TitleFactory', 'ParserFactory', 'GlobalIdGenerator', 'LanguageNameUtils', 'LinkRenderer', 'MagicWordFactory', 'ParsoidParserFactory',],], 'javascript'=>['class'=> 'MediaWiki\\Content\\JavaScriptContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'json'=>['class'=> 'MediaWiki\\Content\\JsonContentHandler', 'services'=>['ParsoidParserFactory', 'TitleFactory',],], 'css'=>['class'=> 'MediaWiki\\Content\\CssContentHandler', 'services'=>['MainConfig', 'ParserFactory', 'UserOptionsLookup',],], 'vue'=>['class'=> 'MediaWiki\\Content\\VueContentHandler', 'services'=>['MainConfig', 'ParserFactory',],], 'text'=> 'MediaWiki\\Content\\TextContentHandler', 'unknown'=> 'MediaWiki\\Content\\FallbackContentHandler',], 'NamespaceContentModels'=>[], 'TextModelsToParse'=>['wikitext', 'javascript', 'css',], 'CompressRevisions'=> false, 'ExternalStores'=>[], 'ExternalServers'=>[], 'DefaultExternalStore'=> false, 'RevisionCacheExpiry'=> 604800, 'PageLanguageUseDB'=> false, 'DiffEngine'=> null, 'ExternalDiffEngine'=> false, 'Wikidiff2Options'=>[], 'RequestTimeLimit'=> null, 'TransactionalTimeLimit'=> 120, 'CriticalSectionTimeLimit'=> 180.0, 'MiserMode'=> false, 'DisableQueryPages'=> false, 'QueryCacheLimit'=> 1000, 'WantedPagesThreshold'=> 1, 'AllowSlowParserFunctions'=> false, 'AllowSchemaUpdates'=> true, 'MaxArticleSize'=> 2048, 'MemoryLimit'=> '50M', 'PoolCounterConf'=> null, 'PoolCountClientConf'=>['servers'=>['127.0.0.1',], 'timeout'=> 0.1,], 'MaxUserDBWriteDuration'=> false, 'MaxJobDBWriteDuration'=> false, 'LinkHolderBatchSize'=> 1000, 'MaximumMovedPages'=> 100, 'ForceDeferredUpdatesPreSend'=> false, 'MultiShardSiteStats'=> false, 'CacheDirectory'=> false, 'MainCacheType'=> 0, 'MessageCacheType'=> -1, 'ParserCacheType'=> -1, 'SessionCacheType'=> -1, 'AnonSessionCacheType'=> false, 'LanguageConverterCacheType'=> -1, 'ObjectCaches'=>[0=>['class'=> 'Wikimedia\\ObjectCache\\EmptyBagOStuff', 'reportDupes'=> false,], 1=>['class'=> 'MediaWiki\\ObjectCache\\SqlBagOStuff', 'loggroup'=> 'SQLBagOStuff',], 'memcached-php'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPhpBagOStuff', 'loggroup'=> 'memcached',], 'memcached-pecl'=>['class'=> 'Wikimedia\\ObjectCache\\MemcachedPeclBagOStuff', 'loggroup'=> 'memcached',], 'hash'=>['class'=> 'Wikimedia\\ObjectCache\\HashBagOStuff', 'reportDupes'=> false,], 'apc'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,], 'apcu'=>['class'=> 'Wikimedia\\ObjectCache\\APCUBagOStuff', 'reportDupes'=> false,],], 'WANObjectCache'=>[], 'MicroStashType'=> -1, 'MainStash'=> 1, 'ParsoidCacheConfig'=>['StashType'=> null, 'StashDuration'=> 86400, 'WarmParsoidParserCache'=> false,], 'ParsoidSelectiveUpdateSampleRate'=> 0, 'ParserCacheFilterConfig'=>['pcache'=>['default'=>['minCpuTime'=> 0,],], 'parsoid-pcache'=>['default'=>['minCpuTime'=> 0,],], 'postproc-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],], 'postproc-parsoid-pcache'=>['default'=>['minCpuTime'=> 9223372036854775807,],],], 'ChronologyProtectorSecret'=> '', 'ParserCacheExpireTime'=> 86400, 'ParserCacheAsyncExpireTime'=> 60, 'ParserCacheAsyncRefreshJobs'=> true, 'OldRevisionParserCacheExpireTime'=> 3600, 'ObjectCacheSessionExpiry'=> 3600, 'PHPSessionHandling'=> 'warn', 'SuspiciousIpExpiry'=> false, 'SessionPbkdf2Iterations'=> 10001, 'UseSessionCookieJwt'=> false, 'MemCachedServers'=>['127.0.0.1:11211',], 'MemCachedPersistent'=> false, 'MemCachedTimeout'=> 500000, 'UseLocalMessageCache'=> false, 'AdaptiveMessageCache'=> false, 'LocalisationCacheConf'=>['class'=> 'MediaWiki\\Language\\LocalisationCache', 'store'=> 'detect', 'storeClass'=> false, 'storeDirectory'=> false, 'storeServer'=>[], 'forceRecache'=> false, 'manualRecache'=> false,], 'CachePages'=> true, 'CacheEpoch'=> '20030516000000', 'GitInfoCacheDirectory'=> false, 'UseFileCache'=> false, 'FileCacheDepth'=> 2, 'RenderHashAppend'=> '', 'EnableSidebarCache'=> false, 'SidebarCacheExpiry'=> 86400, 'UseGzip'=> false, 'InvalidateCacheOnLocalSettingsChange'=> true, 'ExtensionInfoMTime'=> false, 'EnableRemoteBagOStuffTests'=> false, 'UseCdn'=> false, 'VaryOnXFP'=> false, 'InternalServer'=> false, 'CdnMaxAge'=> 18000, 'CdnMaxageLagged'=> 30, 'CdnMaxageStale'=> 10, 'CdnReboundPurgeDelay'=> 0, 'CdnMaxageSubstitute'=> 60, 'ForcedRawSMaxage'=> 300, 'CdnServers'=>[], 'CdnServersNoPurge'=>[], 'HTCPRouting'=>[], 'HTCPMulticastTTL'=> 1, 'UsePrivateIPs'=> false, 'CdnMatchParameterOrder'=> true, 'LanguageCode'=> 'en', 'GrammarForms'=>[], 'InterwikiMagic'=> true, 'HideInterlanguageLinks'=> false, 'ExtraInterlanguageLinkPrefixes'=>[], 'InterlanguageLinkCodeMap'=>[], 'ExtraLanguageNames'=>[], 'ExtraLanguageCodes'=>['bh'=> 'bho', 'no'=> 'nb', 'simple'=> 'en',], 'DummyLanguageCodes'=>[], 'AllUnicodeFixes'=> false, 'LegacyEncoding'=> false, 'AmericanDates'=> false, 'TranslateNumerals'=> true, 'UseDatabaseMessages'=> true, 'MaxMsgCacheEntrySize'=> 10000, 'DisableLangConversion'=> false, 'DisableTitleConversion'=> false, 'DefaultLanguageVariant'=> false, 'UsePigLatinVariant'=> false, 'DisabledVariants'=>[], 'VariantArticlePath'=> false, 'UseXssLanguage'=> false, 'LoginLanguageSelector'=> false, 'ForceUIMsgAsContentMsg'=>[], 'RawHtmlMessages'=>[], 'Localtimezone'=> null, 'LocalTZoffset'=> null, 'OverrideUcfirstCharacters'=>[], 'MimeType'=> 'text/html', 'Html5Version'=> null, 'EditSubmitButtonLabelPublish'=> false, 'XhtmlNamespaces'=>[], 'SiteNotice'=> '', 'BrowserFormatDetection'=> 'telephone=no', 'SkinMetaTags'=>[], 'DefaultSkin'=> 'vector-2022', 'FallbackSkin'=> 'fallback', 'SkipSkins'=>[], 'DisableOutputCompression'=> false, 'FragmentMode'=>['html5', 'legacy',], 'ExternalInterwikiFragmentMode'=> 'legacy', 'FooterIcons'=>['copyright'=>['copyright'=>[],], 'poweredby'=>['mediawiki'=>['src'=> null, 'url'=> 'https:'alt'=> 'Powered by MediaWiki', 'lang'=> 'en',],],], 'UseCombinedLoginLink'=> false, 'Edititis'=> false, 'Send404Code'=> true, 'ShowRollbackEditCount'=> 10, 'EnableCanonicalServerLink'=> false, 'InterwikiLogoOverride'=>[], 'ResourceModules'=>[], 'ResourceModuleSkinStyles'=>[], 'ResourceLoaderSources'=>[], 'ResourceBasePath'=> null, 'ResourceLoaderMaxage'=>[], 'ResourceLoaderDebug'=> false, 'ResourceLoaderMaxQueryLength'=> false, 'ResourceLoaderValidateJS'=> true, 'ResourceLoaderEnableJSProfiler'=> false, 'ResourceLoaderStorageEnabled'=> true, 'ResourceLoaderStorageVersion'=> 1, 'ResourceLoaderEnableSourceMapLinks'=> true, 'AllowSiteCSSOnRestrictedPages'=> false, 'VueDevelopmentMode'=> false, 'CodexDevelopmentDir'=> null, 'MetaNamespace'=> false, 'MetaNamespaceTalk'=> false, 'CanonicalNamespaceNames'=>[-2=> 'Media', -1=> 'Special', 0=> '', 1=> 'Talk', 2=> 'User', 3=> 'User_talk', 4=> 'Project', 5=> 'Project_talk', 6=> 'File', 7=> 'File_talk', 8=> 'MediaWiki', 9=> 'MediaWiki_talk', 10=> 'Template', 11=> 'Template_talk', 12=> 'Help', 13=> 'Help_talk', 14=> 'Category', 15=> 'Category_talk',], 'ExtraNamespaces'=>[], 'ExtraGenderNamespaces'=>[], 'NamespaceAliases'=>[], 'LegalTitleChars'=> ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', 'CapitalLinks' => true, 'CapitalLinkOverrides' => [ ], 'NamespacesWithSubpages' => [ 1 => true, 2 => true, 3 => true, 4 => true, 5 => true, 7 => true, 8 => true, 9 => true, 10 => true, 11 => true, 12 => true, 13 => true, 15 => true, ], 'ContentNamespaces' => [ 0, ], 'ShortPagesNamespaceExclusions' => [ ], 'ExtraSignatureNamespaces' => [ ], 'InvalidRedirectTargets' => [ 'Filepath', 'Mypage', 'Mytalk', 'Redirect', 'Mylog', ], 'DisableHardRedirects' => false, 'FixDoubleRedirects' => false, 'LocalInterwikis' => [ ], 'InterwikiExpiry' => 10800, 'InterwikiCache' => false, 'InterwikiScopes' => 3, 'InterwikiFallbackSite' => 'wiki', 'RedirectSources' => false, 'SiteTypes' => [ 'mediawiki' => 'MediaWiki\\Site\\MediaWikiSite', ], 'MaxTocLevel' => 999, 'MaxPPNodeCount' => 1000000, 'MaxTemplateDepth' => 100, 'MaxPPExpandDepth' => 100, 'UrlProtocols' => [ 'bitcoin:', 'ftp: 'ftps: 'geo:', 'git: 'gopher: 'http: 'https: 'irc: 'ircs: 'magnet:', 'mailto:', 'matrix:', 'mms: 'news:', 'nntp: 'redis: 'sftp: 'sip:', 'sips:', 'sms:', 'ssh: 'svn: 'tel:', 'telnet: 'urn:', 'wikipedia: 'worldwind: 'xmpp:', ' ], 'CleanSignatures' => true, 'AllowExternalImages' => false, 'AllowExternalImagesFrom' => '', 'EnableImageWhitelist' => false, 'TidyConfig' => [ ], 'ParsoidSettings' => [ 'useSelser' => true, ], 'ParsoidExperimentalParserFunctionOutput' => false, 'UseLegacyMediaStyles' => false, 'RawHtml' => false, 'ExternalLinkTarget' => false, 'NoFollowLinks' => true, 'NoFollowNsExceptions' => [ ], 'NoFollowDomainExceptions' => [ 'mediawiki.org', ], 'RegisterInternalExternals' => false, 'ExternalLinksIgnoreDomains' => [ ], 'AllowDisplayTitle' => true, 'RestrictDisplayTitle' => true, 'ExpensiveParserFunctionLimit' => 100, 'PreprocessorCacheThreshold' => 1000, 'EnableScaryTranscluding' => false, 'TranscludeCacheExpiry' => 3600, 'EnableMagicLinks' => [ 'ISBN' => false, 'PMID' => false, 'RFC' => false, ], 'ParserEnableUserLanguage' => false, 'ArticleCountMethod' => 'link', 'ActiveUserDays' => 30, 'LearnerEdits' => 10, 'LearnerMemberSince' => 4, 'ExperiencedUserEdits' => 500, 'ExperiencedUserMemberSince' => 30, 'ManualRevertSearchRadius' => 15, 'RevertedTagMaxDepth' => 15, 'CentralIdLookupProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\CentralId\\LocalIdLookup', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', 'HideUserUtils', ], ], ], 'CentralIdLookupProvider' => 'local', 'UserRegistrationProviders' => [ 'local' => [ 'class' => 'MediaWiki\\User\\Registration\\LocalUserRegistrationProvider', 'services' => [ 'ConnectionProvider', ], ], ], 'PasswordPolicy' => [ 'policies' => [ 'bureaucrat' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'sysop' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'interface-admin' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'bot' => [ 'MinimalPasswordLength' => 10, 'MinimumPasswordLengthToLogin' => 1, ], 'default' => [ 'MinimalPasswordLength' => [ 'value' => 8, 'suggestChangeOnLogin' => true, ], 'PasswordCannotBeSubstringInUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'PasswordCannotMatchDefaults' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], 'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true, ], 'PasswordNotInCommonList' => [ 'value' => true, 'suggestChangeOnLogin' => true, ], ], ], 'checks' => [ 'MinimalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimalPasswordLength', ], 'MinimumPasswordLengthToLogin' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMinimumPasswordLengthToLogin', ], 'PasswordCannotBeSubstringInUsername' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotBeSubstringInUsername', ], 'PasswordCannotMatchDefaults' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordCannotMatchDefaults', ], 'MaximalPasswordLength' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkMaximalPasswordLength', ], 'PasswordNotInCommonList' => [ 'MediaWiki\\Password\\PasswordPolicyChecks', 'checkPasswordNotInCommonList', ], ], ], 'AuthManagerConfig' => null, 'AuthManagerAutoConfig' => [ 'preauth' => [ 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ThrottlePreAuthenticationProvider', 'sort' => 0, ], ], 'primaryauth' => [ 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\TemporaryPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', 'UserOptionsLookup', ], 'args' => [ [ 'authoritative' => false, ], ], 'sort' => 0, ], 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\LocalPasswordPrimaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'args' => [ [ 'authoritative' => true, ], ], 'sort' => 100, ], ], 'secondaryauth' => [ 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\CheckBlocksSecondaryAuthenticationProvider', 'sort' => 0, ], 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\ResetPasswordSecondaryAuthenticationProvider', 'sort' => 100, ], 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider' => [ 'class' => 'MediaWiki\\Auth\\EmailNotificationSecondaryAuthenticationProvider', 'services' => [ 'DBLoadBalancerFactory', ], 'sort' => 200, ], ], ], 'RememberMe' => 'choose', 'ReauthenticateTime' => [ 'default' => 3600, ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'default' => true, ], 'ChangeCredentialsBlacklist' => [ 'MediaWiki\\Auth\\TemporaryPasswordAuthenticationRequest', ], 'RemoveCredentialsBlacklist' => [ 'MediaWiki\\Auth\\PasswordAuthenticationRequest', ], 'InvalidPasswordReset' => true, 'PasswordDefault' => 'pbkdf2', 'PasswordConfig' => [ 'A' => [ 'class' => 'MediaWiki\\Password\\MWOldPassword', ], 'B' => [ 'class' => 'MediaWiki\\Password\\MWSaltedPassword', ], 'pbkdf2-legacyA' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'A', 'pbkdf2', ], ], 'pbkdf2-legacyB' => [ 'class' => 'MediaWiki\\Password\\LayeredParameterizedPassword', 'types' => [ 'B', 'pbkdf2', ], ], 'bcrypt' => [ 'class' => 'MediaWiki\\Password\\BcryptPassword', 'cost' => 9, ], 'pbkdf2' => [ 'class' => 'MediaWiki\\Password\\Pbkdf2PasswordUsingOpenSSL', 'algo' => 'sha512', 'cost' => '30000', 'length' => '64', ], 'argon2' => [ 'class' => 'MediaWiki\\Password\\Argon2Password', 'algo' => 'auto', ], ], 'PasswordResetRoutes' => [ 'username' => true, 'email' => true, ], 'MaxSigChars' => 255, 'SignatureValidation' => 'warning', 'SignatureAllowedLintErrors' => [ 'obsolete-tag', ], 'MaxNameChars' => 255, 'ReservedUsernames' => [ 'MediaWiki default', 'Conversion script', 'Maintenance script', 'Template namespace initialisation script', 'ScriptImporter', 'Delete page script', 'Move page script', 'Command line script', 'Unknown user', 'msg:double-redirect-fixer', 'msg:usermessage-editor', 'msg:proxyblocker', 'msg:sorbs', 'msg:spambot_username', 'msg:autochange-username', ], 'DefaultUserOptions' => [ 'ccmeonemails' => 0, 'date' => 'default', 'diffonly' => 0, 'diff-type' => 'table', 'disablemail' => 0, 'editfont' => 'monospace', 'editondblclick' => 0, 'editrecovery' => 0, 'editsectiononrightclick' => 0, 'email-allow-new-users' => 1, 'enotifminoredits' => 0, 'enotifrevealaddr' => 0, 'enotifusertalkpages' => 1, 'enotifwatchlistpages' => 1, 'extendwatchlist' => 1, 'fancysig' => 0, 'forceeditsummary' => 0, 'forcesafemode' => 0, 'gender' => 'unknown', 'hidecategorization' => 1, 'hideminor' => 0, 'hidepatrolled' => 0, 'imagesize' => 2, 'minordefault' => 0, 'newpageshidepatrolled' => 0, 'nickname' => '', 'norollbackdiff' => 0, 'prefershttps' => 1, 'previewonfirst' => 0, 'previewontop' => 1, 'pst-cssjs' => 1, 'rcdays' => 7, 'rcenhancedfilters-disable' => 0, 'rclimit' => 50, 'requireemail' => 0, 'search-match-redirect' => true, 'search-special-page' => 'Search', 'search-thumbnail-extra-namespaces' => true, 'searchlimit' => 20, 'showhiddencats' => 0, 'shownumberswatching' => 1, 'showrollbackconfirmation' => 0, 'skin' => false, 'skin-responsive' => 1, 'thumbsize' => 5, 'underline' => 2, 'useeditwarning' => 1, 'uselivepreview' => 0, 'usenewrc' => 1, 'watchcreations' => 1, 'watchcreations-expiry' => 'infinite', 'watchdefault' => 1, 'watchdefault-expiry' => 'infinite', 'watchdeletion' => 0, 'watchlistdays' => 7, 'watchlisthideanons' => 0, 'watchlisthidebots' => 0, 'watchlisthidecategorization' => 1, 'watchlisthideliu' => 0, 'watchlisthideminor' => 0, 'watchlisthideown' => 0, 'watchlisthidepatrolled' => 0, 'watchlistreloadautomatically' => 0, 'watchlistunwatchlinks' => 0, 'watchmoves' => 0, 'watchrollback' => 0, 'watchuploads' => 1, 'watchrollback-expiry' => 'infinite', 'watchstar-expiry' => 'infinite', 'wlenhancedfilters-disable' => 0, 'wllimit' => 250, ], 'ConditionalUserOptions' => [ ], 'HiddenPrefs' => [ ], 'UserJsPrefLimit' => 100, 'InvalidUsernameCharacters' => '@:>=', 'UserrightsInterwikiDelimiter' => '@', 'SecureLogin' => false, 'AuthenticationTokenVersion' => null, 'SessionProviders' => [ 'MediaWiki\\Session\\CookieSessionProvider' => [ 'class' => 'MediaWiki\\Session\\CookieSessionProvider', 'args' => [ [ 'priority' => 30, ], ], 'services' => [ 'JwtCodec', 'UrlUtils', ], ], 'MediaWiki\\Session\\BotPasswordSessionProvider' => [ 'class' => 'MediaWiki\\Session\\BotPasswordSessionProvider', 'args' => [ [ 'priority' => 75, ], ], 'services' => [ 'GrantsInfo', ], ], ], 'AutoCreateTempUser' => [ 'known' => false, 'enabled' => false, 'actions' => [ 'edit', ], 'genPattern' => '~$1', 'matchPattern' => null, 'reservedPattern' => '~$1', 'serialProvider' => [ 'type' => 'local', 'useYear' => true, ], 'serialMapping' => [ 'type' => 'readable-numeric', ], 'expireAfterDays' => 90, 'notifyBeforeExpirationDays' => 10, ], 'AutoblockExemptions' => [ ], 'AutoblockExpiry' => 86400, 'BlockAllowsUTEdit' => true, 'BlockCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 19, ], 'BlockDisablesLogin' => false, 'EnableMultiBlocks' => false, 'WhitelistRead' => false, 'WhitelistReadRegexp' => false, 'EmailConfirmToEdit' => false, 'HideIdentifiableRedirects' => true, 'GroupPermissions' => [ '*' => [ 'createaccount' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'viewmyprivateinfo' => true, 'editmyprivateinfo' => true, 'editmyoptions' => true, ], 'user' => [ 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'movefile' => true, 'read' => true, 'edit' => true, 'createpage' => true, 'createtalk' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'minoredit' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, 'editmyuserjsredirect' => true, 'sendemail' => true, 'applychangetags' => true, 'changetags' => true, 'viewmywatchlist' => true, 'editmywatchlist' => true, ], 'autoconfirmed' => [ 'autoconfirmed' => true, 'editsemiprotected' => true, ], 'bot' => [ 'bot' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'nominornewtalk' => true, 'autopatrol' => true, 'suppressredirect' => true, 'apihighlimits' => true, ], 'sysop' => [ 'block' => true, 'createaccount' => true, 'delete' => true, 'bigdelete' => true, 'deletedhistory' => true, 'deletedtext' => true, 'undelete' => true, 'editcontentmodel' => true, 'editinterface' => true, 'editsitejson' => true, 'edituserjson' => true, 'import' => true, 'importupload' => true, 'move' => true, 'move-subpages' => true, 'move-rootuserpages' => true, 'move-categorypages' => true, 'patrol' => true, 'autopatrol' => true, 'protect' => true, 'editprotected' => true, 'rollback' => true, 'upload' => true, 'reupload' => true, 'reupload-shared' => true, 'unwatchedpages' => true, 'autoconfirmed' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'blockemail' => true, 'markbotedits' => true, 'apihighlimits' => true, 'browsearchive' => true, 'noratelimit' => true, 'movefile' => true, 'unblockself' => true, 'suppressredirect' => true, 'mergehistory' => true, 'managechangetags' => true, 'deletechangetags' => true, ], 'interface-admin' => [ 'editinterface' => true, 'editsitecss' => true, 'editsitejson' => true, 'editsitejs' => true, 'editusercss' => true, 'edituserjson' => true, 'edituserjs' => true, ], 'bureaucrat' => [ 'userrights' => true, 'noratelimit' => true, 'renameuser' => true, ], 'suppress' => [ 'hideuser' => true, 'suppressrevision' => true, 'viewsuppressed' => true, 'suppressionlog' => true, 'deleterevision' => true, 'deletelogentry' => true, ], ], 'PrivilegedGroups' => [ 'bureaucrat', 'interface-admin', 'suppress', 'sysop', ], 'RevokePermissions' => [ ], 'GroupInheritsPermissions' => [ ], 'ImplicitGroups' => [ '*', 'user', 'autoconfirmed', ], 'GroupsAddToSelf' => [ ], 'GroupsRemoveFromSelf' => [ ], 'RestrictedGroups' => [ ], 'UserRequirementsPrivateConditions' => [ ], 'RestrictionTypes' => [ 'create', 'edit', 'move', 'upload', ], 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop', ], 'CascadingRestrictionLevels' => [ 'sysop', ], 'SemiprotectedRestrictionLevels' => [ 'autoconfirmed', ], 'NamespaceProtection' => [ ], 'NonincludableNamespaces' => [ ], 'AutoConfirmAge' => 0, 'AutoConfirmCount' => 0, 'Autopromote' => [ 'autoconfirmed' => [ '&', [ 1, null, ], [ 2, null, ], ], ], 'AutopromoteOnce' => [ 'onEdit' => [ ], ], 'AutopromoteOnceLogInRC' => true, 'AutopromoteOnceRCExcludedGroups' => [ ], 'AddGroups' => [ ], 'RemoveGroups' => [ ], 'AvailableRights' => [ ], 'ImplicitRights' => [ ], 'DeleteRevisionsLimit' => 0, 'DeleteRevisionsBatchSize' => 1000, 'HideUserContribLimit' => 1000, 'AccountCreationThrottle' => [ [ 'count' => 0, 'seconds' => 86400, ], ], 'TempAccountCreationThrottle' => [ [ 'count' => 1, 'seconds' => 600, ], [ 'count' => 6, 'seconds' => 86400, ], ], 'TempAccountNameAcquisitionThrottle' => [ [ 'count' => 60, 'seconds' => 86400, ], ], 'SpamRegex' => [ ], 'SummarySpamRegex' => [ ], 'EnableDnsBlacklist' => false, 'DnsBlacklistUrls' => [ ], 'ProxyList' => [ ], 'ProxyWhitelist' => [ ], 'SoftBlockRanges' => [ ], 'ApplyIpBlocksToXff' => false, 'RateLimits' => [ 'edit' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], 'user' => [ 90, 60, ], ], 'move' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], 'upload' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'rollback' => [ 'user' => [ 10, 60, ], 'newbie' => [ 5, 120, ], ], 'mailpassword' => [ 'ip' => [ 5, 3600, ], ], 'sendemail' => [ 'ip' => [ 5, 86400, ], 'newbie' => [ 5, 86400, ], 'user' => [ 20, 86400, ], ], 'changeemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'confirmemail' => [ 'ip-all' => [ 10, 3600, ], 'user' => [ 4, 86400, ], ], 'purge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'linkpurge' => [ 'ip' => [ 30, 60, ], 'user' => [ 30, 60, ], ], 'renderfile' => [ 'ip' => [ 700, 30, ], 'user' => [ 700, 30, ], ], 'renderfile-nonstandard' => [ 'ip' => [ 70, 30, ], 'user' => [ 70, 30, ], ], 'stashedit' => [ 'ip' => [ 30, 60, ], 'newbie' => [ 30, 60, ], ], 'stashbasehtml' => [ 'ip' => [ 5, 60, ], 'newbie' => [ 5, 60, ], ], 'changetags' => [ 'ip' => [ 8, 60, ], 'newbie' => [ 8, 60, ], ], 'editcontentmodel' => [ 'newbie' => [ 2, 120, ], 'user' => [ 8, 60, ], ], ], 'RateLimitsExcludedIPs' => [ ], 'PutIPinRC' => true, 'QueryPageDefaultLimit' => 50, 'ExternalQuerySources' => [ ], 'PasswordAttemptThrottle' => [ [ 'count' => 5, 'seconds' => 300, ], [ 'count' => 150, 'seconds' => 172800, ], ], 'GrantPermissions' => [ 'basic' => [ 'autocreateaccount' => true, 'autoconfirmed' => true, 'autopatrol' => true, 'editsemiprotected' => true, 'ipblock-exempt' => true, 'nominornewtalk' => true, 'patrolmarks' => true, 'read' => true, 'unwatchedpages' => true, ], 'highvolume' => [ 'bot' => true, 'apihighlimits' => true, 'noratelimit' => true, 'markbotedits' => true, ], 'import' => [ 'import' => true, 'importupload' => true, ], 'editpage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'pagelang' => true, ], 'editprotected' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, ], 'editmycssjs' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editmyusercss' => true, 'editmyuserjson' => true, 'editmyuserjs' => true, ], 'editmyoptions' => [ 'editmyoptions' => true, 'editmyuserjson' => true, ], 'editinterface' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, ], 'editsiteconfig' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editinterface' => true, 'edituserjson' => true, 'editsitejson' => true, 'editusercss' => true, 'edituserjs' => true, 'editsitecss' => true, 'editsitejs' => true, ], 'createeditmovepage' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'createpage' => true, 'createtalk' => true, 'delete-redirect' => true, 'move' => true, 'move-rootuserpages' => true, 'move-subpages' => true, 'move-categorypages' => true, 'suppressredirect' => true, ], 'uploadfile' => [ 'upload' => true, 'reupload-own' => true, ], 'uploadeditmovefile' => [ 'upload' => true, 'reupload-own' => true, 'reupload' => true, 'reupload-shared' => true, 'upload_by_url' => true, 'movefile' => true, 'suppressredirect' => true, ], 'patrol' => [ 'patrol' => true, ], 'rollback' => [ 'rollback' => true, ], 'blockusers' => [ 'block' => true, 'blockemail' => true, ], 'viewdeleted' => [ 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, ], 'viewrestrictedlogs' => [ 'suppressionlog' => true, ], 'delete' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'browsearchive' => true, 'deletedhistory' => true, 'deletedtext' => true, 'delete' => true, 'bigdelete' => true, 'deletelogentry' => true, 'deleterevision' => true, 'undelete' => true, ], 'oversight' => [ 'suppressrevision' => true, 'viewsuppressed' => true, ], 'protect' => [ 'edit' => true, 'minoredit' => true, 'applychangetags' => true, 'changetags' => true, 'editcontentmodel' => true, 'editprotected' => true, 'protect' => true, ], 'viewmywatchlist' => [ 'viewmywatchlist' => true, ], 'editmywatchlist' => [ 'editmywatchlist' => true, ], 'sendemail' => [ 'sendemail' => true, ], 'createaccount' => [ 'createaccount' => true, ], 'privateinfo' => [ 'viewmyprivateinfo' => true, ], 'mergehistory' => [ 'mergehistory' => true, ], ], 'GrantPermissionGroups' => [ 'basic' => 'hidden', 'editpage' => 'page-interaction', 'createeditmovepage' => 'page-interaction', 'editprotected' => 'page-interaction', 'patrol' => 'page-interaction', 'uploadfile' => 'file-interaction', 'uploadeditmovefile' => 'file-interaction', 'sendemail' => 'email', 'viewmywatchlist' => 'watchlist-interaction', 'editviewmywatchlist' => 'watchlist-interaction', 'editmycssjs' => 'customization', 'editmyoptions' => 'customization', 'editinterface' => 'administration', 'editsiteconfig' => 'administration', 'rollback' => 'administration', 'blockusers' => 'administration', 'delete' => 'administration', 'viewdeleted' => 'administration', 'viewrestrictedlogs' => 'administration', 'protect' => 'administration', 'oversight' => 'administration', 'createaccount' => 'administration', 'mergehistory' => 'administration', 'import' => 'administration', 'highvolume' => 'high-volume', 'privateinfo' => 'private-information', ], 'GrantRiskGroups' => [ 'basic' => 'low', 'editpage' => 'low', 'createeditmovepage' => 'low', 'editprotected' => 'vandalism', 'patrol' => 'low', 'uploadfile' => 'low', 'uploadeditmovefile' => 'low', 'sendemail' => 'security', 'viewmywatchlist' => 'low', 'editviewmywatchlist' => 'low', 'editmycssjs' => 'security', 'editmyoptions' => 'security', 'editinterface' => 'vandalism', 'editsiteconfig' => 'security', 'rollback' => 'low', 'blockusers' => 'vandalism', 'delete' => 'vandalism', 'viewdeleted' => 'vandalism', 'viewrestrictedlogs' => 'security', 'protect' => 'vandalism', 'oversight' => 'security', 'createaccount' => 'low', 'mergehistory' => 'vandalism', 'import' => 'security', 'highvolume' => 'low', 'privateinfo' => 'low', ], 'EnableBotPasswords' => true, 'BotPasswordsCluster' => false, 'BotPasswordsDatabase' => false, 'SecretKey' => false, 'JwtPrivateKey' => false, 'JwtPublicKey' => false, 'AllowUserJs' => false, 'AllowUserCss' => false, 'AllowUserCssPrefs' => true, 'UseSiteJs' => true, 'UseSiteCss' => true, 'BreakFrames' => false, 'EditPageFrameOptions' => 'DENY', 'ApiFrameOptions' => 'DENY', 'CSPHeader' => false, 'CSPReportOnlyHeader' => false, 'CSPFalsePositiveUrls' => [ 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'https: 'chrome-extension' => true, ], 'AllowCrossOrigin' => false, 'RestAllowCrossOriginCookieAuth' => false, 'SessionSecret' => false, 'CookieExpiration' => 2592000, 'ExtendedLoginCookieExpiration' => 15552000, 'SessionCookieJwtExpiration' => 14400, 'CookieDomain' => '', 'CookiePath' => '/', 'CookieSecure' => 'detect', 'CookiePrefix' => false, 'CookieHttpOnly' => true, 'CookieSameSite' => null, 'CacheVaryCookies' => [ ], 'SessionName' => false, 'CookieSetOnAutoblock' => true, 'CookieSetOnIpBlock' => true, 'DebugLogFile' => '', 'DebugLogPrefix' => '', 'DebugRedirects' => false, 'DebugRawPage' => false, 'DebugComments' => false, 'DebugDumpSql' => false, 'TrxProfilerLimits' => [ 'GET' => [ 'masterConns' => 0, 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'POST-nonwrite' => [ 'writes' => 0, 'readQueryTime' => 5, 'readQueryRows' => 10000, ], 'PostSend-GET' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 10000, 'maxAffected' => 1000, 'masterConns' => 0, 'writes' => 0, ], 'PostSend-POST' => [ 'readQueryTime' => 5, 'writeQueryTime' => 1, 'readQueryRows' => 100000, 'maxAffected' => 1000, ], 'JobRunner' => [ 'readQueryTime' => 30, 'writeQueryTime' => 5, 'readQueryRows' => 100000, 'maxAffected' => 500, ], 'Maintenance' => [ 'writeQueryTime' => 5, 'maxAffected' => 1000, ], ], 'DebugLogGroups' => [ ], 'MWLoggerDefaultSpi' => [ 'class' => 'MediaWiki\\Logger\\LegacySpi', ], 'ShowDebug' => false, 'SpecialVersionShowHooks' => false, 'ShowExceptionDetails' => false, 'LogExceptionBacktrace' => true, 'PropagateErrors' => true, 'ShowHostnames' => false, 'OverrideHostname' => false, 'DevelopmentWarnings' => false, 'DeprecationReleaseLimit' => false, 'Profiler' => [ ], 'StatsdServer' => false, 'StatsdMetricPrefix' => 'MediaWiki', 'StatsTarget' => null, 'StatsFormat' => null, 'StatsPrefix' => 'mediawiki', 'OpenTelemetryConfig' => null, 'PageInfoTransclusionLimit' => 50, 'EnableJavaScriptTest' => false, 'CachePrefix' => false, 'DebugToolbar' => false, 'DisableTextSearch' => false, 'AdvancedSearchHighlighting' => false, 'SearchHighlightBoundaries' => '[\\p{Z}\\p{P}\\p{C}]', 'OpenSearchTemplates' => [ 'application/x-suggestions+json' => false, 'application/x-suggestions+xml' => false, ], 'OpenSearchDefaultLimit' => 10, 'OpenSearchDescriptionLength' => 100, 'SearchSuggestCacheExpiry' => 1200, 'DisableSearchUpdate' => false, 'NamespacesToBeSearchedDefault' => [ true, ], 'DisableInternalSearch' => false, 'SearchForwardUrl' => null, 'SitemapNamespaces' => false, 'SitemapNamespacesPriorities' => false, 'SitemapApiConfig' => [ ], 'SpecialSearchFormOptions' => [ ], 'SearchMatchRedirectPreference' => false, 'SearchRunSuggestedQuery' => true, 'Diff3' => '/usr/bin/diff3', 'Diff' => '/usr/bin/diff', 'PreviewOnOpenNamespaces' => [ 14 => true, ], 'UniversalEditButton' => true, 'UseAutomaticEditSummaries' => true, 'CommandLineDarkBg' => false, 'ReadOnly' => null, 'ReadOnlyWatchedItemStore' => false, 'ReadOnlyFile' => false, 'UpgradeKey' => false, 'GitBin' => '/usr/bin/git', 'GitRepositoryViewers' => [ 'https: 'ssh: ], 'InstallerInitialPages' => [ [ 'titlemsg' => 'mainpage', 'text' => '{{subst:int:mainpagetext}}{{subst:int:mainpagedocfooter}}', ], ], 'RCMaxAge' => 7776000, 'WatchersMaxAge' => 15552000, 'UnwatchedPageSecret' => 1, 'RCFilterByAge' => false, 'RCLinkLimits' => [ 50, 100, 250, 500, ], 'RCLinkDays' => [ 1, 3, 7, 14, 30, ], 'RCFeeds' => [ ], 'RCEngines' => [ 'redis' => 'MediaWiki\\RCFeed\\RedisPubSubFeedEngine', 'udp' => 'MediaWiki\\RCFeed\\UDPRCFeedEngine', ], 'RCWatchCategoryMembership' => false, 'UseRCPatrol' => true, 'StructuredChangeFiltersLiveUpdatePollingRate' => 3, 'UseNPPatrol' => true, 'UseFilePatrol' => true, 'Feed' => true, 'FeedLimit' => 50, 'FeedCacheTimeout' => 60, 'FeedDiffCutoff' => 32768, 'OverrideSiteFeed' => [ ], 'FeedClasses' => [ 'rss' => 'MediaWiki\\Feed\\RSSFeed', 'atom' => 'MediaWiki\\Feed\\AtomFeed', ], 'AdvertisedFeedTypes' => [ 'atom', ], 'RCShowWatchingUsers' => false, 'RCShowChangedSize' => true, 'RCChangedSizeThreshold' => 500, 'ShowUpdatedMarker' => true, 'DisableAnonTalk' => false, 'UseTagFilter' => true, 'SoftwareTags' => [ 'mw-contentmodelchange' => true, 'mw-new-redirect' => true, 'mw-removed-redirect' => true, 'mw-changed-redirect-target' => true, 'mw-blank' => true, 'mw-replace' => true, 'mw-recreated' => true, 'mw-rollback' => true, 'mw-undo' => true, 'mw-manual-revert' => true, 'mw-reverted' => true, 'mw-server-side-upload' => true, 'mw-ipblock-appeal' => true, ], 'UnwatchedPageThreshold' => false, 'RecentChangesFlags' => [ 'newpage' => [ 'letter' => 'newpageletter', 'title' => 'recentchanges-label-newpage', 'legend' => 'recentchanges-legend-newpage', 'grouping' => 'any', ], 'minor' => [ 'letter' => 'minoreditletter', 'title' => 'recentchanges-label-minor', 'legend' => 'recentchanges-legend-minor', 'class' => 'minoredit', 'grouping' => 'all', ], 'bot' => [ 'letter' => 'boteditletter', 'title' => 'recentchanges-label-bot', 'legend' => 'recentchanges-legend-bot', 'class' => 'botedit', 'grouping' => 'all', ], 'unpatrolled' => [ 'letter' => 'unpatrolledletter', 'title' => 'recentchanges-label-unpatrolled', 'legend' => 'recentchanges-legend-unpatrolled', 'grouping' => 'any', ], ], 'WatchlistExpiry' => false, 'EnableWatchlistLabels' => false, 'WatchlistLabelsMaxPerUser' => 100, 'WatchlistPurgeRate' => 0.1, 'WatchlistExpiryMaxDuration' => '1 year', 'EnableChangesListQueryPartitioning' => false, 'RightsPage' => null, 'RightsUrl' => null, 'RightsText' => null, 'RightsIcon' => null, 'UseCopyrightUpload' => false, 'MaxCredits' => 0, 'ShowCreditsIfMax' => true, 'ImportSources' => [ ], 'ImportTargetNamespace' => null, 'ExportAllowHistory' => true, 'ExportMaxHistory' => 0, 'ExportAllowListContributors' => false, 'ExportMaxLinkDepth' => 0, 'ExportFromNamespaces' => false, 'ExportAllowAll' => false, 'ExportPagelistLimit' => 5000, 'XmlDumpSchemaVersion' => '0.11', 'WikiFarmSettingsDirectory' => null, 'WikiFarmSettingsExtension' => 'yaml', 'ExtensionFunctions' => [ ], 'ExtensionMessagesFiles' => [ ], 'MessagesDirs' => [ ], 'TranslationAliasesDirs' => [ ], 'ExtensionEntryPointListFiles' => [ ], 'EnableParserLimitReporting' => true, 'ValidSkinNames' => [ ], 'SpecialPages' => [ ], 'ExtensionCredits' => [ ], 'Hooks' => [ ], 'ServiceWiringFiles' => [ ], 'JobClasses' => [ 'deletePage' => 'MediaWiki\\Page\\DeletePageJob', 'refreshLinks' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'deleteLinks' => 'MediaWiki\\Page\\DeleteLinksJob', 'htmlCacheUpdate' => 'MediaWiki\\JobQueue\\Jobs\\HTMLCacheUpdateJob', 'sendMail' => [ 'class' => 'MediaWiki\\Mail\\EmaillingJob', 'services' => [ 'Emailer', ], ], 'enotifNotify' => [ 'class' => 'MediaWiki\\RecentChanges\\RecentChangeNotifyJob', 'services' => [ 'RecentChangeLookup', ], ], 'fixDoubleRedirect' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\DoubleRedirectJob', 'services' => [ 'RevisionLookup', 'MagicWordFactory', 'WikiPageFactory', ], 'needsPage' => true, ], 'AssembleUploadChunks' => 'MediaWiki\\JobQueue\\Jobs\\AssembleUploadChunksJob', 'PublishStashedFile' => 'MediaWiki\\JobQueue\\Jobs\\PublishStashedFileJob', 'ThumbnailRender' => 'MediaWiki\\JobQueue\\Jobs\\ThumbnailRenderJob', 'UploadFromUrl' => 'MediaWiki\\JobQueue\\Jobs\\UploadFromUrlJob', 'recentChangesUpdate' => 'MediaWiki\\RecentChanges\\RecentChangesUpdateJob', 'refreshLinksPrioritized' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'refreshLinksDynamic' => 'MediaWiki\\JobQueue\\Jobs\\RefreshLinksJob', 'activityUpdateJob' => 'MediaWiki\\Watchlist\\ActivityUpdateJob', 'categoryMembershipChange' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryMembershipChangeJob', 'services' => [ 'RecentChangeFactory', ], ], 'CategoryCountUpdateJob' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\CategoryCountUpdateJob', 'services' => [ 'ConnectionProvider', 'NamespaceInfo', ], ], 'clearUserWatchlist' => 'MediaWiki\\Watchlist\\ClearUserWatchlistJob', 'watchlistExpiry' => 'MediaWiki\\Watchlist\\WatchlistExpiryJob', 'cdnPurge' => 'MediaWiki\\JobQueue\\Jobs\\CdnPurgeJob', 'userGroupExpiry' => 'MediaWiki\\User\\UserGroupExpiryJob', 'clearWatchlistNotifications' => 'MediaWiki\\Watchlist\\ClearWatchlistNotificationsJob', 'userOptionsUpdate' => 'MediaWiki\\User\\Options\\UserOptionsUpdateJob', 'revertedTagUpdate' => 'MediaWiki\\JobQueue\\Jobs\\RevertedTagUpdateJob', 'null' => 'MediaWiki\\JobQueue\\Jobs\\NullJob', 'userEditCountInit' => 'MediaWiki\\User\\UserEditCountInitJob', 'parsoidCachePrewarm' => [ 'class' => 'MediaWiki\\JobQueue\\Jobs\\ParsoidCachePrewarmJob', 'services' => [ 'ParserOutputAccess', 'PageStore', 'RevisionLookup', 'ParsoidSiteConfig', ], 'needsPage' => false, ], 'renameUserTable' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], 'renameUserDerived' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserDerivedJob', 'services' => [ 'RenameUserFactory', 'UserFactory', ], ], 'renameUser' => [ 'class' => 'MediaWiki\\RenameUser\\Job\\RenameUserTableJob', 'services' => [ 'MainConfig', 'DBLoadBalancerFactory', ], ], ], 'JobTypesExcludedFromDefaultQueue' => [ 'AssembleUploadChunks', 'PublishStashedFile', 'UploadFromUrl', ], 'JobBackoffThrottling' => [ ], 'JobTypeConf' => [ 'default' => [ 'class' => 'MediaWiki\\JobQueue\\JobQueueDB', 'order' => 'random', 'claimTTL' => 3600, ], ], 'JobQueueIncludeInMaxLagFactor' => false, 'SpecialPageCacheUpdates' => [ 'Statistics' => [ 'MediaWiki\\Deferred\\SiteStatsUpdate', 'cacheUpdate', ], ], 'PagePropLinkInvalidations' => [ 'hiddencat' => 'categorylinks', ], 'CategoryMagicGallery' => true, 'CategoryPagingLimit' => 200, 'CategoryCollation' => 'uppercase', 'TempCategoryCollations' => [ ], 'SortedCategories' => false, 'TrackingCategories' => [ ], 'LogTypes' => [ '', 'block', 'protect', 'rights', 'delete', 'upload', 'move', 'import', 'interwiki', 'patrol', 'merge', 'suppress', 'tag', 'managetags', 'contentmodel', 'renameuser', ], 'LogRestrictions' => [ 'suppress' => 'suppressionlog', ], 'FilterLogTypes' => [ 'patrol' => true, 'tag' => true, 'newusers' => false, ], 'LogNames' => [ '' => 'all-logs-page', 'block' => 'blocklogpage', 'protect' => 'protectlogpage', 'rights' => 'rightslog', 'delete' => 'dellogpage', 'upload' => 'uploadlogpage', 'move' => 'movelogpage', 'import' => 'importlogpage', 'patrol' => 'patrol-log-page', 'merge' => 'mergelog', 'suppress' => 'suppressionlog', ], 'LogHeaders' => [ '' => 'alllogstext', 'block' => 'blocklogtext', 'delete' => 'dellogpagetext', 'import' => 'importlogpagetext', 'merge' => 'mergelogpagetext', 'move' => 'movelogpagetext', 'patrol' => 'patrol-log-header', 'protect' => 'protectlogtext', 'rights' => 'rightslogtext', 'suppress' => 'suppressionlogtext', 'upload' => 'uploadlogpagetext', ], 'LogActions' => [ ], 'LogActionsHandlers' => [ 'block/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'block/unblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'contentmodel/change' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'contentmodel/new' => 'MediaWiki\\Logging\\ContentModelLogFormatter', 'delete/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/delete_redir2' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/restore' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'delete/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'import/interwiki' => 'MediaWiki\\Logging\\ImportLogFormatter', 'import/upload' => 'MediaWiki\\Logging\\ImportLogFormatter', 'interwiki/iw_add' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_delete' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'interwiki/iw_edit' => 'MediaWiki\\Logging\\InterwikiLogFormatter', 'managetags/activate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/create' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/deactivate' => 'MediaWiki\\Logging\\LogFormatter', 'managetags/delete' => 'MediaWiki\\Logging\\LogFormatter', 'merge/merge' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'merge/merge-into' => [ 'class' => 'MediaWiki\\Logging\\MergeLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'move/move_redir' => [ 'class' => 'MediaWiki\\Logging\\MoveLogFormatter', 'services' => [ 'TitleParser', ], ], 'patrol/patrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'patrol/autopatrol' => 'MediaWiki\\Logging\\PatrolLogFormatter', 'protect/modify' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/move_prot' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/protect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'protect/unprotect' => [ 'class' => 'MediaWiki\\Logging\\ProtectLogFormatter', 'services' => [ 'TitleParser', ], ], 'renameuser/renameuser' => [ 'class' => 'MediaWiki\\Logging\\RenameuserLogFormatter', 'services' => [ 'TitleParser', ], ], 'rights/autopromote' => 'MediaWiki\\Logging\\RightsLogFormatter', 'rights/rights' => 'MediaWiki\\Logging\\RightsLogFormatter', 'suppress/block' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/delete' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/event' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'suppress/reblock' => [ 'class' => 'MediaWiki\\Logging\\BlockLogFormatter', 'services' => [ 'TitleParser', 'NamespaceInfo', ], ], 'suppress/revision' => 'MediaWiki\\Logging\\DeleteLogFormatter', 'tag/update' => 'MediaWiki\\Logging\\TagLogFormatter', 'upload/overwrite' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/revert' => 'MediaWiki\\Logging\\UploadLogFormatter', 'upload/upload' => 'MediaWiki\\Logging\\UploadLogFormatter', ], 'ActionFilteredLogs' => [ 'block' => [ 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], 'unblock' => [ 'unblock', ], ], 'contentmodel' => [ 'change' => [ 'change', ], 'new' => [ 'new', ], ], 'delete' => [ 'delete' => [ 'delete', ], 'delete_redir' => [ 'delete_redir', 'delete_redir2', ], 'restore' => [ 'restore', ], 'event' => [ 'event', ], 'revision' => [ 'revision', ], ], 'import' => [ 'interwiki' => [ 'interwiki', ], 'upload' => [ 'upload', ], ], 'managetags' => [ 'create' => [ 'create', ], 'delete' => [ 'delete', ], 'activate' => [ 'activate', ], 'deactivate' => [ 'deactivate', ], ], 'move' => [ 'move' => [ 'move', ], 'move_redir' => [ 'move_redir', ], ], 'newusers' => [ 'create' => [ 'create', 'newusers', ], 'create2' => [ 'create2', ], 'autocreate' => [ 'autocreate', ], 'byemail' => [ 'byemail', ], ], 'protect' => [ 'protect' => [ 'protect', ], 'modify' => [ 'modify', ], 'unprotect' => [ 'unprotect', ], 'move_prot' => [ 'move_prot', ], ], 'rights' => [ 'rights' => [ 'rights', ], 'autopromote' => [ 'autopromote', ], ], 'suppress' => [ 'event' => [ 'event', ], 'revision' => [ 'revision', ], 'delete' => [ 'delete', ], 'block' => [ 'block', ], 'reblock' => [ 'reblock', ], ], 'upload' => [ 'upload' => [ 'upload', ], 'overwrite' => [ 'overwrite', ], 'revert' => [ 'revert', ], ], ], 'NewUserLog' => true, 'PageCreationLog' => true, 'AllowSpecialInclusion' => true, 'DisableQueryPageUpdate' => false, 'CountCategorizedImagesAsUsed' => false, 'MaxRedirectLinksRetrieved' => 500, 'RangeContributionsCIDRLimit' => [ 'IPv4' => 16, 'IPv6' => 32, ], 'Actions' => [ ], 'DefaultRobotPolicy' => 'index,follow', 'NamespaceRobotPolicies' => [ ], 'ArticleRobotPolicies' => [ ], 'ExemptFromUserRobotsControl' => null, 'DebugAPI' => false, 'APIModules' => [ ], 'APIFormatModules' => [ ], 'APIMetaModules' => [ ], 'APIPropModules' => [ ], 'APIListModules' => [ ], 'APIMaxDBRows' => 5000, 'APIMaxResultSize' => 8388608, 'APIMaxUncachedDiffs' => 1, 'APIMaxLagThreshold' => 7, 'APICacheHelpTimeout' => 3600, 'APIUselessQueryPages' => [ 'MIMEsearch', 'LinkSearch', ], 'AjaxLicensePreview' => true, 'CrossSiteAJAXdomains' => [ ], 'CrossSiteAJAXdomainExceptions' => [ ], 'AllowedCorsHeaders' => [ 'Accept', 'Accept-Language', 'Content-Language', 'Content-Type', 'Accept-Encoding', 'DNT', 'Origin', 'User-Agent', 'Api-User-Agent', 'Access-Control-Max-Age', 'Authorization', ], 'RestAPIAdditionalRouteFiles' => [ ], 'RestSandboxSpecs' => [ ], 'MaxShellMemory' => 307200, 'MaxShellFileSize' => 102400, 'MaxShellTime' => 180, 'MaxShellWallClockTime' => 180, 'ShellCgroup' => false, 'PhpCli' => '/usr/bin/php', 'ShellRestrictionMethod' => 'autodetect', 'ShellboxUrls' => [ 'default' => null, ], 'ShellboxSecretKey' => null, 'ShellboxShell' => '/bin/sh', 'HTTPTimeout' => 25, 'HTTPConnectTimeout' => 5.0, 'HTTPMaxTimeout' => 0, 'HTTPMaxConnectTimeout' => 0, 'HTTPImportTimeout' => 25, 'AsyncHTTPTimeout' => 25, 'HTTPProxy' => '', 'LocalVirtualHosts' => [ ], 'LocalHTTPProxy' => false, 'AllowExternalReqID' => false, 'JobRunRate' => 1, 'RunJobsAsync' => false, 'UpdateRowsPerJob' => 300, 'UpdateRowsPerQuery' => 100, 'RedirectOnLogin' => null, 'VirtualRestConfig' => [ 'paths' => [ ], 'modules' => [ ], 'global' => [ 'timeout' => 360, 'forwardCookies' => false, 'HTTPProxy' => null, ], ], 'EventRelayerConfig' => [ 'default' => [ 'class' => 'Wikimedia\\EventRelayer\\EventRelayerNull', ], ], 'Pingback' => false, 'OriginTrials' => [ ], 'ReportToExpiry' => 86400, 'ReportToEndpoints' => [ ], 'FeaturePolicyReportOnly' => [ ], 'SkinsPreferred' => [ 'vector-2022', 'vector', ], 'SpecialContributeSkinsEnabled' => [ ], 'SpecialContributeNewPageTarget' => null, 'EnableEditRecovery' => false, 'EditRecoveryExpiry' => 2592000, 'UseCodexSpecialBlock' => false, 'ShowLogoutConfirmation' => false, 'EnableProtectionIndicators' => true, 'OutputPipelineStages' => [ ], 'FeatureShutdown' => [ ], 'CloneArticleParserOutput' => true, 'UseLeximorph' => false, 'UsePostprocCache' => false, 'UsePostprocCacheLegacy' => false, 'UsePostprocCacheParsoid' => false, 'ParserOptionsLogUnsafeSampleRate' => 0, ], 'type' => [ 'ConfigRegistry' => 'object', 'AssumeProxiesUseDefaultProtocolPorts' => 'boolean', 'ForceHTTPS' => 'boolean', 'ExtensionDirectory' => [ 'string', 'null', ], 'StyleDirectory' => [ 'string', 'null', ], 'UploadDirectory' => [ 'string', 'boolean', 'null', ], 'Logos' => [ 'object', 'boolean', ], 'ReferrerPolicy' => [ 'array', 'string', 'boolean', ], 'ActionPaths' => 'object', 'MainPageIsDomainRoot' => 'boolean', 'ImgAuthUrlPathMap' => 'object', 'LocalFileRepo' => 'object', 'ForeignFileRepos' => 'array', 'UseSharedUploads' => 'boolean', 'SharedUploadDirectory' => [ 'string', 'null', ], 'SharedUploadPath' => [ 'string', 'null', ], 'HashedSharedUploadDirectory' => 'boolean', 'FetchCommonsDescriptions' => 'boolean', 'SharedUploadDBname' => [ 'boolean', 'string', ], 'SharedUploadDBprefix' => 'string', 'CacheSharedUploads' => 'boolean', 'ForeignUploadTargets' => 'array', 'UploadDialog' => 'object', 'FileBackends' => 'object', 'LockManagers' => 'array', 'CopyUploadsDomains' => 'array', 'CopyUploadTimeout' => [ 'boolean', 'integer', ], 'SharedThumbnailScriptPath' => [ 'string', 'boolean', ], 'HashedUploadDirectory' => 'boolean', 'CSPUploadEntryPoint' => 'boolean', 'FileExtensions' => 'array', 'ProhibitedFileExtensions' => 'array', 'MimeTypeExclusions' => 'array', 'TrustedMediaFormats' => 'array', 'MediaHandlers' => 'object', 'NativeImageLazyLoading' => 'boolean', 'ParserTestMediaHandlers' => 'object', 'MaxInterlacingAreas' => 'object', 'SVGConverters' => 'object', 'SVGNativeRendering' => [ 'string', 'boolean', ], 'MaxImageArea' => [ 'string', 'integer', 'boolean', ], 'TiffThumbnailType' => 'array', 'GenerateThumbnailOnParse' => 'boolean', 'EnableAutoRotation' => [ 'boolean', 'null', ], 'Antivirus' => [ 'string', 'null', ], 'AntivirusSetup' => 'object', 'MimeDetectorCommand' => [ 'string', 'null', ], 'XMLMimeTypes' => 'object', 'ImageLimits' => 'array', 'ThumbLimits' => 'array', 'ThumbnailNamespaces' => 'array', 'ThumbnailSteps' => [ 'array', 'null', ], 'ThumbnailStepsRatio' => [ 'number', 'null', ], 'ThumbnailBuckets' => [ 'array', 'null', ], 'UploadThumbnailRenderMap' => 'object', 'GalleryOptions' => 'object', 'DjvuDump' => [ 'string', 'null', ], 'DjvuRenderer' => [ 'string', 'null', ], 'DjvuTxt' => [ 'string', 'null', ], 'DjvuPostProcessor' => [ 'string', 'null', ], 'UserEmailConfirmationUseHTML' => 'boolean', 'SMTP' => [ 'boolean', 'object', ], 'EnotifFromEditor' => 'boolean', 'EnotifRevealEditorAddress' => 'boolean', 'UsersNotifiedOnAllChanges' => 'object', 'DBmwschema' => [ 'string', 'null', ], 'SharedTables' => 'array', 'DBservers' => [ 'boolean', 'array', ], 'LBFactoryConf' => 'object', 'LocalDatabases' => 'array', 'VirtualDomainsMapping' => 'object', 'FileSchemaMigrationStage' => 'integer', 'ImageLinksSchemaMigrationStage' => 'integer', 'ExternalLinksDomainGaps' => 'object', 'ContentHandlers' => 'object', 'NamespaceContentModels' => 'object', 'TextModelsToParse' => 'array', 'ExternalStores' => 'array', 'ExternalServers' => 'object', 'DefaultExternalStore' => [ 'array', 'boolean', ], 'RevisionCacheExpiry' => 'integer', 'PageLanguageUseDB' => 'boolean', 'DiffEngine' => [ 'string', 'null', ], 'ExternalDiffEngine' => [ 'string', 'boolean', ], 'Wikidiff2Options' => 'object', 'RequestTimeLimit' => [ 'integer', 'null', ], 'CriticalSectionTimeLimit' => 'number', 'PoolCounterConf' => [ 'object', 'null', ], 'PoolCountClientConf' => 'object', 'MaxUserDBWriteDuration' => [ 'integer', 'boolean', ], 'MaxJobDBWriteDuration' => [ 'integer', 'boolean', ], 'MultiShardSiteStats' => 'boolean', 'ObjectCaches' => 'object', 'WANObjectCache' => 'object', 'MicroStashType' => [ 'string', 'integer', ], 'ParsoidCacheConfig' => 'object', 'ParsoidSelectiveUpdateSampleRate' => 'integer', 'ParserCacheFilterConfig' => 'object', 'ChronologyProtectorSecret' => 'string', 'PHPSessionHandling' => 'string', 'SuspiciousIpExpiry' => [ 'integer', 'boolean', ], 'MemCachedServers' => 'array', 'LocalisationCacheConf' => 'object', 'ExtensionInfoMTime' => [ 'integer', 'boolean', ], 'CdnServers' => 'object', 'CdnServersNoPurge' => 'object', 'HTCPRouting' => 'object', 'GrammarForms' => 'object', 'ExtraInterlanguageLinkPrefixes' => 'array', 'InterlanguageLinkCodeMap' => 'object', 'ExtraLanguageNames' => 'object', 'ExtraLanguageCodes' => 'object', 'DummyLanguageCodes' => 'object', 'DisabledVariants' => 'object', 'ForceUIMsgAsContentMsg' => 'object', 'RawHtmlMessages' => 'array', 'OverrideUcfirstCharacters' => 'object', 'XhtmlNamespaces' => 'object', 'BrowserFormatDetection' => 'string', 'SkinMetaTags' => 'object', 'SkipSkins' => 'object', 'FragmentMode' => 'array', 'FooterIcons' => 'object', 'InterwikiLogoOverride' => 'array', 'ResourceModules' => 'object', 'ResourceModuleSkinStyles' => 'object', 'ResourceLoaderSources' => 'object', 'ResourceLoaderMaxage' => 'object', 'ResourceLoaderMaxQueryLength' => [ 'integer', 'boolean', ], 'CanonicalNamespaceNames' => 'object', 'ExtraNamespaces' => 'object', 'ExtraGenderNamespaces' => 'object', 'NamespaceAliases' => 'object', 'CapitalLinkOverrides' => 'object', 'NamespacesWithSubpages' => 'object', 'ContentNamespaces' => 'array', 'ShortPagesNamespaceExclusions' => 'array', 'ExtraSignatureNamespaces' => 'array', 'InvalidRedirectTargets' => 'array', 'LocalInterwikis' => 'array', 'InterwikiCache' => [ 'boolean', 'object', ], 'SiteTypes' => 'object', 'UrlProtocols' => 'array', 'TidyConfig' => 'object', 'ParsoidSettings' => 'object', 'ParsoidExperimentalParserFunctionOutput' => 'boolean', 'NoFollowNsExceptions' => 'array', 'NoFollowDomainExceptions' => 'array', 'ExternalLinksIgnoreDomains' => 'array', 'EnableMagicLinks' => 'object', 'ManualRevertSearchRadius' => 'integer', 'RevertedTagMaxDepth' => 'integer', 'CentralIdLookupProviders' => 'object', 'CentralIdLookupProvider' => 'string', 'UserRegistrationProviders' => 'object', 'PasswordPolicy' => 'object', 'AuthManagerConfig' => [ 'object', 'null', ], 'AuthManagerAutoConfig' => 'object', 'RememberMe' => 'string', 'ReauthenticateTime' => 'object', 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => 'object', 'ChangeCredentialsBlacklist' => 'array', 'RemoveCredentialsBlacklist' => 'array', 'PasswordConfig' => 'object', 'PasswordResetRoutes' => 'object', 'SignatureAllowedLintErrors' => 'array', 'ReservedUsernames' => 'array', 'DefaultUserOptions' => 'object', 'ConditionalUserOptions' => 'object', 'HiddenPrefs' => 'array', 'UserJsPrefLimit' => 'integer', 'AuthenticationTokenVersion' => [ 'string', 'null', ], 'SessionProviders' => 'object', 'AutoCreateTempUser' => 'object', 'AutoblockExemptions' => 'array', 'BlockCIDRLimit' => 'object', 'EnableMultiBlocks' => 'boolean', 'GroupPermissions' => 'object', 'PrivilegedGroups' => 'array', 'RevokePermissions' => 'object', 'GroupInheritsPermissions' => 'object', 'ImplicitGroups' => 'array', 'GroupsAddToSelf' => 'object', 'GroupsRemoveFromSelf' => 'object', 'RestrictedGroups' => 'object', 'UserRequirementsPrivateConditions' => 'array', 'RestrictionTypes' => 'array', 'RestrictionLevels' => 'array', 'CascadingRestrictionLevels' => 'array', 'SemiprotectedRestrictionLevels' => 'array', 'NamespaceProtection' => 'object', 'NonincludableNamespaces' => 'object', 'Autopromote' => 'object', 'AutopromoteOnce' => 'object', 'AutopromoteOnceRCExcludedGroups' => 'array', 'AddGroups' => 'object', 'RemoveGroups' => 'object', 'AvailableRights' => 'array', 'ImplicitRights' => 'array', 'AccountCreationThrottle' => [ 'integer', 'array', ], 'TempAccountCreationThrottle' => 'array', 'TempAccountNameAcquisitionThrottle' => 'array', 'SpamRegex' => 'array', 'SummarySpamRegex' => 'array', 'DnsBlacklistUrls' => 'array', 'ProxyList' => [ 'string', 'array', ], 'ProxyWhitelist' => 'array', 'SoftBlockRanges' => 'array', 'RateLimits' => 'object', 'RateLimitsExcludedIPs' => 'array', 'ExternalQuerySources' => 'object', 'PasswordAttemptThrottle' => 'array', 'GrantPermissions' => 'object', 'GrantPermissionGroups' => 'object', 'GrantRiskGroups' => 'object', 'EnableBotPasswords' => 'boolean', 'BotPasswordsCluster' => [ 'string', 'boolean', ], 'BotPasswordsDatabase' => [ 'string', 'boolean', ], 'CSPHeader' => [ 'boolean', 'object', ], 'CSPReportOnlyHeader' => [ 'boolean', 'object', ], 'CSPFalsePositiveUrls' => 'object', 'AllowCrossOrigin' => 'boolean', 'RestAllowCrossOriginCookieAuth' => 'boolean', 'CookieSameSite' => [ 'string', 'null', ], 'CacheVaryCookies' => 'array', 'TrxProfilerLimits' => 'object', 'DebugLogGroups' => 'object', 'MWLoggerDefaultSpi' => 'object', 'Profiler' => 'object', 'StatsTarget' => [ 'string', 'null', ], 'StatsFormat' => [ 'string', 'null', ], 'StatsPrefix' => 'string', 'OpenTelemetryConfig' => [ 'object', 'null', ], 'OpenSearchTemplates' => 'object', 'NamespacesToBeSearchedDefault' => 'object', 'SitemapNamespaces' => [ 'boolean', 'array', ], 'SitemapNamespacesPriorities' => [ 'boolean', 'object', ], 'SitemapApiConfig' => 'object', 'SpecialSearchFormOptions' => 'object', 'SearchMatchRedirectPreference' => 'boolean', 'SearchRunSuggestedQuery' => 'boolean', 'PreviewOnOpenNamespaces' => 'object', 'ReadOnlyWatchedItemStore' => 'boolean', 'GitRepositoryViewers' => 'object', 'InstallerInitialPages' => 'array', 'RCLinkLimits' => 'array', 'RCLinkDays' => 'array', 'RCFeeds' => 'object', 'RCEngines' => 'object', 'OverrideSiteFeed' => 'object', 'FeedClasses' => 'object', 'AdvertisedFeedTypes' => 'array', 'SoftwareTags' => 'object', 'RecentChangesFlags' => 'object', 'WatchlistExpiry' => 'boolean', 'EnableWatchlistLabels' => 'boolean', 'WatchlistLabelsMaxPerUser' => 'integer', 'WatchlistPurgeRate' => 'number', 'WatchlistExpiryMaxDuration' => [ 'string', 'null', ], 'EnableChangesListQueryPartitioning' => 'boolean', 'ImportSources' => 'object', 'ExtensionFunctions' => 'array', 'ExtensionMessagesFiles' => 'object', 'MessagesDirs' => 'object', 'TranslationAliasesDirs' => 'object', 'ExtensionEntryPointListFiles' => 'object', 'ValidSkinNames' => 'object', 'SpecialPages' => 'object', 'ExtensionCredits' => 'object', 'Hooks' => 'object', 'ServiceWiringFiles' => 'array', 'JobClasses' => 'object', 'JobTypesExcludedFromDefaultQueue' => 'array', 'JobBackoffThrottling' => 'object', 'JobTypeConf' => 'object', 'SpecialPageCacheUpdates' => 'object', 'PagePropLinkInvalidations' => 'object', 'TempCategoryCollations' => 'array', 'SortedCategories' => 'boolean', 'TrackingCategories' => 'array', 'LogTypes' => 'array', 'LogRestrictions' => 'object', 'FilterLogTypes' => 'object', 'LogNames' => 'object', 'LogHeaders' => 'object', 'LogActions' => 'object', 'LogActionsHandlers' => 'object', 'ActionFilteredLogs' => 'object', 'RangeContributionsCIDRLimit' => 'object', 'Actions' => 'object', 'NamespaceRobotPolicies' => 'object', 'ArticleRobotPolicies' => 'object', 'ExemptFromUserRobotsControl' => [ 'array', 'null', ], 'APIModules' => 'object', 'APIFormatModules' => 'object', 'APIMetaModules' => 'object', 'APIPropModules' => 'object', 'APIListModules' => 'object', 'APIUselessQueryPages' => 'array', 'CrossSiteAJAXdomains' => 'object', 'CrossSiteAJAXdomainExceptions' => 'object', 'AllowedCorsHeaders' => 'array', 'RestAPIAdditionalRouteFiles' => 'array', 'RestSandboxSpecs' => 'object', 'ShellRestrictionMethod' => [ 'string', 'boolean', ], 'ShellboxUrls' => 'object', 'ShellboxSecretKey' => [ 'string', 'null', ], 'ShellboxShell' => [ 'string', 'null', ], 'HTTPTimeout' => 'number', 'HTTPConnectTimeout' => 'number', 'HTTPMaxTimeout' => 'number', 'HTTPMaxConnectTimeout' => 'number', 'LocalVirtualHosts' => 'object', 'LocalHTTPProxy' => [ 'string', 'boolean', ], 'VirtualRestConfig' => 'object', 'EventRelayerConfig' => 'object', 'Pingback' => 'boolean', 'OriginTrials' => 'array', 'ReportToExpiry' => 'integer', 'ReportToEndpoints' => 'array', 'FeaturePolicyReportOnly' => 'array', 'SkinsPreferred' => 'array', 'SpecialContributeSkinsEnabled' => 'array', 'SpecialContributeNewPageTarget' => [ 'string', 'null', ], 'EnableEditRecovery' => 'boolean', 'EditRecoveryExpiry' => 'integer', 'UseCodexSpecialBlock' => 'boolean', 'ShowLogoutConfirmation' => 'boolean', 'EnableProtectionIndicators' => 'boolean', 'OutputPipelineStages' => 'object', 'FeatureShutdown' => 'array', 'CloneArticleParserOutput' => 'boolean', 'UseLeximorph' => 'boolean', 'UsePostprocCache' => 'boolean', 'UsePostprocCacheLegacy' => 'boolean', 'UsePostprocCacheParsoid' => 'boolean', 'ParserOptionsLogUnsafeSampleRate' => 'integer', ], 'mergeStrategy' => [ 'TiffThumbnailType' => 'replace', 'LBFactoryConf' => 'replace', 'InterwikiCache' => 'replace', 'PasswordPolicy' => 'array_replace_recursive', 'AuthManagerAutoConfig' => 'array_plus_2d', 'GroupPermissions' => 'array_plus_2d', 'RevokePermissions' => 'array_plus_2d', 'AddGroups' => 'array_merge_recursive', 'RemoveGroups' => 'array_merge_recursive', 'RateLimits' => 'array_plus_2d', 'GrantPermissions' => 'array_plus_2d', 'MWLoggerDefaultSpi' => 'replace', 'Profiler' => 'replace', 'Hooks' => 'array_merge_recursive', 'VirtualRestConfig' => 'array_plus_2d', ], 'dynamicDefault' => [ 'UsePathInfo' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUsePathInfo', ], ], 'Script' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultScript', ], ], 'LoadScript' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLoadScript', ], ], 'RestPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultRestPath', ], ], 'StylePath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultStylePath', ], ], 'LocalStylePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalStylePath', ], ], 'ExtensionAssetsPath' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultExtensionAssetsPath', ], ], 'ArticlePath' => [ 'use' => [ 'Script', 'UsePathInfo', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultArticlePath', ], ], 'UploadPath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultUploadPath', ], ], 'FileCacheDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultFileCacheDirectory', ], ], 'Logo' => [ 'use' => [ 'ResourceBasePath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLogo', ], ], 'DeletedDirectory' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDeletedDirectory', ], ], 'ShowEXIF' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultShowEXIF', ], ], 'SharedPrefix' => [ 'use' => [ 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedPrefix', ], ], 'SharedSchema' => [ 'use' => [ 'DBmwschema', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultSharedSchema', ], ], 'DBerrorLogTZ' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultDBerrorLogTZ', ], ], 'Localtimezone' => [ 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocaltimezone', ], ], 'LocalTZoffset' => [ 'use' => [ 'Localtimezone', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultLocalTZoffset', ], ], 'ResourceBasePath' => [ 'use' => [ 'ScriptPath', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultResourceBasePath', ], ], 'MetaNamespace' => [ 'use' => [ 'Sitename', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultMetaNamespace', ], ], 'CookieSecure' => [ 'use' => [ 'ForceHTTPS', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookieSecure', ], ], 'CookiePrefix' => [ 'use' => [ 'SharedDB', 'SharedPrefix', 'SharedTables', 'DBname', 'DBprefix', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultCookiePrefix', ], ], 'ReadOnlyFile' => [ 'use' => [ 'UploadDirectory', ], 'callback' => [ 'MediaWiki\\MainConfigSchema', 'getDefaultReadOnlyFile', ], ], ], ], 'config-schema' => [ 'UploadStashScalerBaseUrl' => [ 'deprecated' => 'since 1.36 Use thumbProxyUrl in $wgLocalFileRepo', ], 'IllegalFileChars' => [ 'deprecated' => 'since 1.41; no longer customizable', ], 'ThumbnailNamespaces' => [ 'items' => [ 'type' => 'integer', ], ], 'LocalDatabases' => [ 'items' => [ 'type' => 'string', ], ], 'ParserCacheFilterConfig' => [ 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of namespace IDs to filter definitions.', 'additionalProperties' => [ 'type' => 'object', 'description' => 'A map of filter names to values.', 'properties' => [ 'minCpuTime' => [ 'type' => 'number', ], ], ], ], ], 'PHPSessionHandling' => [ 'deprecated' => 'since 1.45 Integration with PHP session handling will be removed in the future', ], 'RawHtmlMessages' => [ 'items' => [ 'type' => 'string', ], ], 'InterwikiLogoOverride' => [ 'items' => [ 'type' => 'string', ], ], 'LegalTitleChars' => [ 'deprecated' => 'since 1.41; use Extension:TitleBlacklist to customize', ], 'ReauthenticateTime' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'AllowSecuritySensitiveOperationIfCannotReauthenticate' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'ChangeCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'RemoveCredentialsBlacklist' => [ 'items' => [ 'type' => 'string', ], ], 'GroupPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GroupInheritsPermissions' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'AvailableRights' => [ 'items' => [ 'type' => 'string', ], ], 'ImplicitRights' => [ 'items' => [ 'type' => 'string', ], ], 'SoftBlockRanges' => [ 'items' => [ 'type' => 'string', ], ], 'ExternalQuerySources' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'enabled' => [ 'type' => 'boolean', 'default' => false, ], 'url' => [ 'type' => 'string', 'format' => 'uri', ], 'timeout' => [ 'type' => 'integer', 'default' => 10, ], ], 'required' => [ 'enabled', 'url', ], 'additionalProperties' => false, ], ], 'GrantPermissions' => [ 'additionalProperties' => [ 'type' => 'object', 'additionalProperties' => [ 'type' => 'boolean', ], ], ], 'GrantPermissionGroups' => [ 'additionalProperties' => [ 'type' => 'string', ], ], 'SitemapNamespacesPriorities' => [ 'deprecated' => 'since 1.45 and ignored', ], 'SitemapApiConfig' => [ 'additionalProperties' => [ 'enabled' => [ 'type' => 'bool', ], 'sitemapsPerIndex' => [ 'type' => 'int', ], 'pagesPerSitemap' => [ 'type' => 'int', ], 'expiry' => [ 'type' => 'int', ], ], ], 'SoftwareTags' => [ 'additionalProperties' => [ 'type' => 'boolean', ], ], 'JobBackoffThrottling' => [ 'additionalProperties' => [ 'type' => 'number', ], ], 'JobTypeConf' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'class' => [ 'type' => 'string', ], 'order' => [ 'type' => 'string', ], 'claimTTL' => [ 'type' => 'integer', ], ], ], ], 'TrackingCategories' => [ 'deprecated' => 'since 1.25 Extensions should now register tracking categories using the new extension registration system.', ], 'RangeContributionsCIDRLimit' => [ 'additionalProperties' => [ 'type' => 'integer', ], ], 'RestSandboxSpecs' => [ 'additionalProperties' => [ 'type' => 'object', 'properties' => [ 'url' => [ 'type' => 'string', 'format' => 'url', ], 'name' => [ 'type' => 'string', ], 'msg' => [ 'type' => 'string', 'description' => 'a message key', ], ], 'required' => [ 'url', ], ], ], 'ShellboxUrls' => [ 'additionalProperties' => [ 'type' => [ 'string', 'boolean', 'null', ], ], ], ], 'obsolete-config' => [ 'MangleFlashPolicy' => 'Since 1.39; no longer has any effect.', 'EnableOpenSearchSuggest' => 'Since 1.35, no longer used', 'AutoloadAttemptLowercase' => 'Since 1.40; no longer has any effect.', ],]
Authentication providers are used by AuthManager when authenticating users.
getUniqueId()
Return a unique identifier for this instance.
Pre-authentication providers can prevent authentication early on.
Primary authentication providers associate submitted input data with a MediaWiki account.
Secondary providers act after input data is already associated with a MediaWiki account.
Interface for configuration instances.
Definition Config.php:18
Interface for objects (potentially) representing an editable wiki page.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
isNamed()
Is the user a normal non-temporary registered user?
getUser()
Returns the performer of the actions associated with this authority.
isTemp()
Is the user an autocreated temporary user?
authorizeWrite(string $action, PageIdentity $target, ?PermissionStatus $status=null)
Authorize write access.
probablyCan(string $action, PageIdentity $target, ?PermissionStatus $status=null)
Checks whether this authority can probably perform the given action on the given target page.
MediaWiki\Session entry point interface.
Service for looking up UserIdentity.
Interface for objects representing user identity.
Shared interface for rigor levels when dealing with User methods.
Interface for database access objects.
This class is a delegate to ILBFactory for a given database cluster.
$source