MediaWiki master
LoginSignupSpecialPage.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\SpecialPage;
25
27use Exception;
28use FatalError;
29use LogicException;
30use LoginHelper;
53use Skin;
54use StatusValue;
55use Wikimedia\ScopedCallback;
56
64
72 protected string $mReturnTo;
78 protected string $mReturnToQuery;
85 protected string $mReturnToAnchor;
86
88 protected $mPosted;
90 protected $mAction;
92 protected $mToken;
94 protected $mStickHTTPS;
96 protected $mFromHTTP;
98 protected $mEntryError = '';
100 protected $mEntryErrorType = 'error';
102 protected $mDisplay = 'page';
103
105 protected $mLoaded = false;
107 protected $mLoadedRequest = false;
111 private $reasonValidatorResult = null;
112
114 protected $securityLevel;
115
121 protected $targetUser;
122
124 protected $authForm;
125
126 abstract protected function isSignup();
127
134 abstract protected function successfulAction( $direct = false, $extraMessages = null );
135
142 abstract protected function logAuthResult( $success, UserIdentity $performer, $status = null );
143
144 protected function setRequest( array $data, $wasPosted = null ) {
145 parent::setRequest( $data, $wasPosted );
146 $this->mLoadedRequest = false;
147 }
148
152 private function loadRequestParameters() {
153 if ( $this->mLoadedRequest ) {
154 return;
155 }
156 $this->mLoadedRequest = true;
157 $request = $this->getRequest();
158
159 $this->mPosted = $request->wasPosted();
160 $this->mAction = $request->getRawVal( 'action' );
161 $this->mFromHTTP = $request->getBool( 'fromhttp', false )
162 || $request->getBool( 'wpFromhttp', false );
163 $this->mStickHTTPS = $this->getConfig()->get( MainConfigNames::ForceHTTPS )
164 || ( !$this->mFromHTTP && $request->getProtocol() === 'https' )
165 || $request->getBool( 'wpForceHttps', false );
166 $this->mReturnTo = $request->getVal( 'returnto', '' );
167 $this->mReturnToQuery = $request->getVal( 'returntoquery', '' );
168 $this->mReturnToAnchor = $request->getVal( 'returntoanchor', '' );
169 if ( $request->getRawVal( 'display' ) === 'popup' ) {
170 $this->mDisplay = 'popup';
171 }
172 }
173
179 protected function load( $subPage ) {
180 $this->loadRequestParameters();
181 if ( $this->mLoaded ) {
182 return;
183 }
184 $this->mLoaded = true;
185 $request = $this->getRequest();
186
187 $securityLevel = $this->getRequest()->getText( 'force' );
188 if (
189 $securityLevel &&
190 MediaWikiServices::getInstance()->getAuthManager()->securitySensitiveOperationStatus(
191 $securityLevel ) === AuthManager::SEC_REAUTH
192 ) {
193 $this->securityLevel = $securityLevel;
194 }
195
196 $this->loadAuth( $subPage );
197
198 $this->mToken = $request->getVal( $this->getTokenName() );
199
200 // Show an error or warning or a notice passed on from a previous page
201 $entryError = $this->msg( $request->getVal( 'error', '' ) );
202 $entryWarning = $this->msg( $request->getVal( 'warning', '' ) );
203 $entryNotice = $this->msg( $request->getVal( 'notice', '' ) );
204 // bc: provide login link as a parameter for messages where the translation
205 // was not updated
206 $loginreqlink = $this->getLinkRenderer()->makeKnownLink(
207 $this->getPageTitle(),
208 $this->msg( 'loginreqlink' )->text(),
209 [],
210 $this->getPreservedParams( [ 'reset' => true ] )
211 );
212
213 // Only show valid error or warning messages.
214 $validErrorMessages = LoginHelper::getValidErrorMessages();
215 if ( $entryError->exists()
216 && in_array( $entryError->getKey(), $validErrorMessages, true )
217 ) {
218 $this->mEntryErrorType = 'error';
219 $this->mEntryError = $entryError->rawParams( $loginreqlink )->parse();
220
221 } elseif ( $entryWarning->exists()
222 && in_array( $entryWarning->getKey(), $validErrorMessages, true )
223 ) {
224 $this->mEntryErrorType = 'warning';
225 $this->mEntryError = $entryWarning->rawParams( $loginreqlink )->parse();
226 } elseif ( $entryNotice->exists()
227 && in_array( $entryNotice->getKey(), $validErrorMessages, true )
228 ) {
229 $this->mEntryErrorType = 'notice';
230 $this->mEntryError = $entryNotice->parse();
231 }
232
233 # 1. When switching accounts, it sucks to get automatically logged out
234 # 2. Do not return to PasswordReset after a successful password change
235 # but goto Wiki start page (Main_Page) instead ( T35997 )
236 $returnToTitle = Title::newFromText( $this->mReturnTo );
237 if ( is_object( $returnToTitle )
238 && ( $returnToTitle->isSpecial( 'Userlogout' )
239 || $returnToTitle->isSpecial( 'PasswordReset' ) )
240 ) {
241 $this->mReturnTo = '';
242 $this->mReturnToQuery = '';
243 }
244 }
245
247 protected function getPreservedParams( $options = [] ) {
248 $params = parent::getPreservedParams( $options );
249
250 // Override returnto* with their property-based values, to account for the
251 // special-casing in load().
252 $this->loadRequestParameters();
253 $properties = [
254 'returnto' => 'mReturnTo',
255 'returntoquery' => 'mReturnToQuery',
256 'returntoanchor' => 'mReturnToAnchor',
257 ];
258 foreach ( $properties as $key => $prop ) {
259 $value = $this->$prop;
260 if ( $value !== '' ) {
261 $params[$key] = $value;
262 } else {
263 unset( $params[$key] );
264 }
265 }
266
267 if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->isSignup() ) {
268 $params['fromhttp'] = $this->mFromHTTP ? '1' : null;
269 }
270 if ( $this->mDisplay !== 'page' ) {
271 $params['display'] = $this->mDisplay;
272 }
273
274 return array_filter( $params, static fn ( $val ) => $val !== null );
275 }
276
277 protected function beforeExecute( $subPage ) {
278 // finish initializing the class before processing the request - T135924
279 $this->loadRequestParameters();
280 return parent::beforeExecute( $subPage );
281 }
282
286 public function execute( $subPage ) {
287 if ( $this->mPosted ) {
288 $time = microtime( true );
289 $profilingScope = new ScopedCallback( function () use ( $time ) {
290 $time = microtime( true ) - $time;
291 $stats = MediaWikiServices::getInstance()->getStatsFactory();
292 $stats->getTiming( 'auth_specialpage_executeTiming_seconds' )
293 ->setLabel( 'action', $this->authAction )
294 ->copyToStatsdAt( "timing.login.ui.{$this->authAction}" )
295 ->observe( $time * 1000 );
296 } );
297 }
298
299 $authManager = MediaWikiServices::getInstance()->getAuthManager();
300 $session = SessionManager::getGlobalSession();
301
302 // Session data is used for various things in the authentication process, so we must make
303 // sure a session cookie or some equivalent mechanism is set.
304 $session->persist();
305 // Explicitly disable cache to ensure cookie blocks may be set (T152462).
306 // (Technically redundant with sessions persisting from this page.)
307 $this->getOutput()->disableClientCache();
308
309 $this->load( $subPage );
310
311 // Do this early, so that it affects how error pages are rendered too
312 if ( $this->mDisplay === 'popup' ) {
313 // Replace the default skin with a "micro-skin" that omits most of the interface. (T362706)
314 // In the future, we might allow normal skins to serve this mode too, if they advise that
315 // they support it by setting a skin option, so that colors and fonts could stay consistent.
316 $skinFactory = MediaWikiServices::getInstance()->getSkinFactory();
317 $this->getContext()->setSkin( $skinFactory->makeSkin( 'authentication-popup' ) );
318 }
319
320 $this->setHeaders();
321 $this->checkPermissions();
322
323 // Make sure the system configuration allows log in / sign up
324 if ( !$this->isSignup() && !$authManager->canAuthenticateNow() ) {
325 if ( !$session->canSetUser() ) {
326 throw new ErrorPageError( 'cannotloginnow-title', 'cannotloginnow-text', [
327 $session->getProvider()->describe( $this->getLanguage() )
328 ] );
329 }
330 throw new ErrorPageError( 'cannotlogin-title', 'cannotlogin-text' );
331 } elseif ( $this->isSignup() && !$authManager->canCreateAccounts() ) {
332 throw new ErrorPageError( 'cannotcreateaccount-title', 'cannotcreateaccount-text' );
333 }
334
335 /*
336 * In the case where the user is already logged in, and was redirected to
337 * the login form from a page that requires login, do not show the login
338 * page. The use case scenario for this is when a user opens a large number
339 * of tabs, is redirected to the login page on all of them, and then logs
340 * in on one, expecting all the others to work properly.
341 *
342 * However, do show the form if it was visited intentionally (no 'returnto'
343 * is present). People who often switch between several accounts have grown
344 * accustomed to this behavior.
345 *
346 * For temporary users, the form is always shown, since the UI presents
347 * temporary users as not logged in and offers to discard their temporary
348 * account by logging in.
349 *
350 * Also make an exception when force=<level> is set in the URL, which means the user must
351 * reauthenticate for security reasons.
352 */
353 if ( !$this->isSignup() && !$this->mPosted && !$this->securityLevel &&
354 ( $this->mReturnTo !== '' || $this->mReturnToQuery !== '' ) &&
355 !$this->getUser()->isTemp() && $this->getUser()->isRegistered()
356 ) {
357 $this->successfulAction();
358 return;
359 }
360
361 // If logging in and not on HTTPS, either redirect to it or offer a link.
362 if ( $this->getRequest()->getProtocol() !== 'https' ) {
363 $title = $this->getFullTitle();
364 $query = $this->getPreservedParams() + [
365 'title' => null,
366 ( $this->mEntryErrorType === 'error' ? 'error'
367 : 'warning' ) => $this->mEntryError,
368 ] + $this->getRequest()->getQueryValues();
369 $url = $title->getFullURL( $query, false, PROTO_HTTPS );
370 if ( $this->getConfig()->get( MainConfigNames::SecureLogin ) && !$this->mFromHTTP ) {
371 // Avoid infinite redirect
372 $url = wfAppendQuery( $url, 'fromhttp=1' );
373 $this->getOutput()->redirect( $url );
374 // Since we only do this redir to change proto, always vary
375 $this->getOutput()->addVaryHeader( 'X-Forwarded-Proto' );
376
377 return;
378 } else {
379 // A wiki without HTTPS login support should set $wgServer to
380 // http://somehost, in which case the secure URL generated
381 // above won't actually start with https://
382 if ( str_starts_with( $url, 'https://' ) ) {
383 $this->mSecureLoginUrl = $url;
384 }
385 }
386 }
387
388 if ( !$this->isActionAllowed( $this->authAction ) ) {
389 // FIXME how do we explain this to the user? can we handle session loss better?
390 // messages used: authpage-cannot-login, authpage-cannot-login-continue,
391 // authpage-cannot-create, authpage-cannot-create-continue
392 $this->mainLoginForm( [], 'authpage-cannot-' . $this->authAction );
393 return;
394 }
395
396 if ( $this->canBypassForm( $button_name ) ) {
397 $this->setRequest( [], true );
398 $this->getRequest()->setVal( $this->getTokenName(), $this->getToken() );
399 if ( $button_name ) {
400 $this->getRequest()->setVal( $button_name, true );
401 }
402 }
403 $performer = $this->getUser();
404 $status = $this->trySubmit();
405
406 if ( !$status || !$status->isGood() ) {
407 $this->mainLoginForm( $this->authRequests, $status ? $status->getMessage() : '', 'error' );
408 return;
409 }
410
412 $response = $status->getValue();
413
414 $returnToUrl = $this->getPageTitle( 'return' )
415 ->getFullURL( $this->getPreservedParams( [ 'withToken' => true ] ), false, PROTO_HTTPS );
416 switch ( $response->status ) {
417 case AuthenticationResponse::PASS:
418 $this->logAuthResult( true, $performer );
419 $this->proxyAccountCreation = $this->isSignup() && $this->getUser()->isNamed();
420 $this->targetUser = User::newFromName( $response->username );
421
422 if (
423 !$this->proxyAccountCreation
424 && $response->loginRequest
425 && $authManager->canAuthenticateNow()
426 ) {
427 // successful registration; log the user in instantly
428 $response2 = $authManager->beginAuthentication( [ $response->loginRequest ],
429 $returnToUrl );
430 if ( $response2->status !== AuthenticationResponse::PASS ) {
431 LoggerFactory::getInstance( 'login' )
432 ->error( 'Could not log in after account creation' );
433 $this->successfulAction( true, Status::newFatal( 'createacct-loginerror' ) );
434 break;
435 }
436 }
437
438 if ( !$this->proxyAccountCreation ) {
439 $context = RequestContext::getMain();
440 $localContext = $this->getContext();
441 if ( $context !== $localContext ) {
442 // remove AuthManagerSpecialPage context hack
443 $this->setContext( $context );
444 }
445 // Ensure that the context user is the same as the session user.
446 $this->getAuthManager()->setRequestContextUserFromSessionUser();
447 }
448
449 $this->successfulAction( true );
450 break;
451 case AuthenticationResponse::FAIL:
452 // fall through
453 case AuthenticationResponse::RESTART:
454 $this->authForm = null;
455 if ( $response->status === AuthenticationResponse::FAIL ) {
456 $action = $this->getDefaultAction( $subPage );
457 $messageType = 'error';
458 } else {
459 $action = $this->getContinueAction( $this->authAction );
460 $messageType = 'warning';
461 }
462 $this->logAuthResult( false, $performer, $response->message ? $response->message->getKey() : '-' );
463 $this->loadAuth( $subPage, $action, true );
464 $this->mainLoginForm( $this->authRequests, $response->message, $messageType );
465 break;
466 case AuthenticationResponse::REDIRECT:
467 $this->authForm = null;
468 $this->getOutput()->redirect( $response->redirectTarget );
469 break;
470 case AuthenticationResponse::UI:
471 $this->authForm = null;
472 $this->authAction = $this->isSignup() ? AuthManager::ACTION_CREATE_CONTINUE
473 : AuthManager::ACTION_LOGIN_CONTINUE;
474 $this->authRequests = $response->neededRequests;
475 $this->mainLoginForm( $response->neededRequests, $response->message, $response->messageType );
476 break;
477 default:
478 throw new LogicException( 'invalid AuthenticationResponse' );
479 }
480 }
481
495 private function canBypassForm( &$button_name ) {
496 $button_name = null;
497 if ( $this->isContinued() ) {
498 return false;
499 }
500 $fields = AuthenticationRequest::mergeFieldInfo( $this->authRequests );
501 foreach ( $fields as $fieldname => $field ) {
502 if ( !isset( $field['type'] ) ) {
503 return false;
504 }
505 if ( !empty( $field['skippable'] ) ) {
506 continue;
507 }
508 if ( $field['type'] === 'button' ) {
509 if ( $button_name !== null ) {
510 $button_name = null;
511 return false;
512 } else {
513 $button_name = $fieldname;
514 }
515 } elseif ( $field['type'] !== 'null' ) {
516 return false;
517 }
518 }
519 return true;
520 }
521
531 protected function showSuccessPage(
532 $type, $title, $msgname, $injected_html, $extraMessages
533 ) {
534 $out = $this->getOutput();
535 if ( is_string( $title ) ) {
536 wfDeprecated( __METHOD__ . ' with string title', '1.41' ); // T343849
537 $title = ( new RawMessage( '$1' ) )->rawParams( $title );
538 }
539 $out->setPageTitleMsg( $title );
540 if ( $msgname ) {
541 $out->addWikiMsg( $msgname, wfEscapeWikiText( $this->getUser()->getName() ) );
542 }
543 if ( $extraMessages ) {
544 $extraMessages = Status::wrap( $extraMessages );
545 $out->addWikiTextAsInterface(
546 $extraMessages->getWikiText( false, false, $this->getLanguage() )
547 );
548 }
549
550 $out->addHTML( $injected_html );
551
552 $helper = new LoginHelper( $this->getContext() );
553 $helper->showReturnToPage( $type, $this->mReturnTo, $this->mReturnToQuery,
554 $this->mStickHTTPS, $this->mReturnToAnchor );
555 }
556
570 protected function mainLoginForm( array $requests, $msg = '', $msgtype = 'error' ) {
571 $user = $this->getUser();
572 $out = $this->getOutput();
573
574 // FIXME how to handle empty $requests - restart, or no form, just an error message?
575 // no form would be better for no session type errors, restart is better when can* fails.
576 if ( !$requests ) {
577 $this->authAction = $this->getDefaultAction( $this->subPage );
578 $this->authForm = null;
579 $requests = MediaWikiServices::getInstance()->getAuthManager()
580 ->getAuthenticationRequests( $this->authAction, $user );
581 }
582
583 // Generic styles and scripts for both login and signup form
584 $out->addModuleStyles( [
585 'mediawiki.special.userlogin.common.styles',
586 'mediawiki.codex.messagebox.styles'
587 ] );
588 if ( $this->isSignup() ) {
589 // Additional styles and scripts for signup form
590 $out->addModules( 'mediawiki.special.createaccount' );
591 $out->addModuleStyles( [
592 'mediawiki.special.userlogin.signup.styles'
593 ] );
594 } else {
595 // Additional styles for login form
596 $out->addModuleStyles( [
597 'mediawiki.special.userlogin.login.styles'
598 ] );
599 }
600 $out->disallowUserJs(); // just in case...
601
602 $form = $this->getAuthForm( $requests, $this->authAction );
603 $form->prepareForm();
604
605 $submitStatus = Status::newGood();
606 if ( $msg && $msgtype === 'warning' ) {
607 $submitStatus->warning( $msg );
608 } elseif ( $msg && $msgtype === 'error' ) {
609 $submitStatus->fatal( $msg );
610 }
611
612 // warning header for non-standard workflows (e.g. security reauthentication)
613 if (
614 !$this->isSignup() &&
615 $this->getUser()->isRegistered() &&
616 !$this->getUser()->isTemp() &&
617 $this->authAction !== AuthManager::ACTION_LOGIN_CONTINUE
618 ) {
619 $reauthMessage = $this->securityLevel ? 'userlogin-reauth' : 'userlogin-loggedin';
620 $submitStatus->warning( $reauthMessage, $this->getUser()->getName() );
621 }
622
623 $formHtml = $form->getHTML( $submitStatus );
624
625 $out->addHTML( $this->getPageHtml( $formHtml ) );
626 }
627
634 protected function getPageHtml( $formHtml ) {
635 $loginPrompt = $this->isSignup() ? '' : Html::rawElement( 'div',
636 [ 'id' => 'userloginprompt' ], $this->msg( 'loginprompt' )->parseAsBlock() );
637 $languageLinks = $this->getConfig()->get( MainConfigNames::LoginLanguageSelector )
638 ? $this->makeLanguageSelector() : '';
639 $signupStartMsg = $this->msg( 'signupstart' );
640 $signupStart = ( $this->isSignup() && !$signupStartMsg->isDisabled() )
641 ? Html::rawElement( 'div', [ 'id' => 'signupstart' ], $signupStartMsg->parseAsBlock() ) : '';
642 if ( $languageLinks ) {
643 $languageLinks = Html::rawElement( 'div', [ 'id' => 'languagelinks' ],
644 Html::rawElement( 'p', [], $languageLinks )
645 );
646 }
647 if ( $this->getUser()->isTemp() ) {
648 $noticeHtml = $this->getNoticeHtml();
649 } else {
650 $noticeHtml = '';
651 }
652 $formBlock = Html::rawElement( 'div', [ 'id' => 'userloginForm' ], $formHtml );
653 $formAndBenefits = $formBlock;
654 if ( $this->isSignup() && $this->showExtraInformation() ) {
655 $benefitsContainerHtml = null;
656 $info = [
657 'context' => $this->getContext(),
658 'form' => $this->authForm,
659 ];
660 $options = [
661 'beforeForm' => false,
662 ];
663 $this->getHookRunner()->onSpecialCreateAccountBenefits(
664 $benefitsContainerHtml, $info, $options
665 );
666 $benefitsContainerHtml ??= $this->getBenefitsContainerHtml();
667 $formAndBenefits = $options['beforeForm']
668 ? ( $benefitsContainerHtml . $formBlock )
669 : ( $formBlock . $benefitsContainerHtml );
670 }
671
672 return $loginPrompt
673 . $languageLinks
674 . $signupStart
675 . $noticeHtml
676 . Html::rawElement( 'div', [ 'class' => 'mw-ui-container' ],
677 $formAndBenefits
678 );
679 }
680
688 protected function getBenefitsContainerHtml(): string {
689 $benefitsContainer = '';
690 $this->getOutput()->addModuleStyles( [ 'oojs-ui.styles.icons-user' ] );
691 if ( $this->isSignup() && $this->showExtraInformation() ) {
692 if ( !$this->getUser()->isTemp() ) {
693 // The following messages are used here:
694 // * createacct-benefit-icon1 createacct-benefit-head1 createacct-benefit-body1
695 // * createacct-benefit-icon2 createacct-benefit-head2 createacct-benefit-body2
696 // * createacct-benefit-icon3 createacct-benefit-head3 createacct-benefit-body3
697 $benefitCount = 3;
698 $benefitList = '';
699 for ( $benefitIdx = 1; $benefitIdx <= $benefitCount; $benefitIdx++ ) {
700 $headUnescaped = $this->msg( "createacct-benefit-head$benefitIdx" )->text();
701 $iconClass = $this->msg( "createacct-benefit-icon$benefitIdx" )->text();
702 $benefitList .= Html::rawElement( 'div', [ 'class' => "mw-number-text $iconClass" ],
703 Html::rawElement( 'span', [],
704 $this->msg( "createacct-benefit-head$benefitIdx" )->escaped()
705 )
706 . Html::rawElement( 'p', [],
707 $this->msg( "createacct-benefit-body$benefitIdx" )->params( $headUnescaped )->escaped()
708 )
709 );
710 }
711 $benefitsContainer = Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-container' ],
712 Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-heading' ],
713 $this->msg( 'createacct-benefit-heading' )->escaped()
714 )
715 . Html::rawElement( 'div', [ 'class' => 'mw-createacct-benefits-list' ], $benefitList )
716 );
717 } else {
718 $benefitList = '';
719 $this->getOutput()->addModuleStyles(
720 [
721 'oojs-ui.styles.icons-moderation',
722 'oojs-ui.styles.icons-interactions',
723 ]
724 );
725 $benefits = [
726 [
727 'icon' => 'oo-ui-icon-unStar',
728 'description' => $this->msg( "benefit-1-description" )->escaped()
729 ],
730 [
731 'icon' => 'oo-ui-icon-userContributions',
732 'description' => $this->msg( "benefit-2-description" )->escaped()
733 ],
734 [
735 'icon' => 'oo-ui-icon-settings',
736 'description' => $this->msg( "benefit-3-description" )->escaped()
737 ]
738 ];
739 foreach ( $benefits as $benefit ) {
740 $benefitContent = Html::rawElement( 'div', [ 'class' => 'mw-benefit-item' ],
741 Html::rawElement( 'span', [ 'class' => $benefit[ 'icon' ] ] )
742 . Html::rawElement( 'p', [], $benefit['description'] )
743 );
744
745 $benefitList .= Html::rawElement(
746 'div', [ 'class' => 'mw-benefit-item-wrapper' ], $benefitContent );
747 }
748
749 $benefitsListWrapper = Html::rawElement(
750 'div', [ 'class' => 'mw-benefit-list-wrapper' ], $benefitList );
751
752 $headingSubheadingWrapper = Html::rawElement( 'div', [ 'class' => 'mw-heading-subheading-wrapper' ],
753 Html::rawElement( 'h2', [], $this->msg( 'createacct-benefit-heading-temp-user' )->escaped() )
754 . Html::rawElement( 'p', [ 'class' => 'mw-benefit-subheading' ], $this->msg(
755 'createacct-benefit-subheading-temp-user' )->escaped() )
756 );
757
758 $benefitsContainer = Html::rawElement(
759 'div', [ 'class' => 'mw-createacct-benefits-container' ],
760 $headingSubheadingWrapper
761 . $benefitsListWrapper
762 );
763 }
764 }
765 return $benefitsContainer;
766 }
767
774 protected function getAuthForm( array $requests, $action ) {
775 // FIXME merge this with parent
776
777 if ( $this->authForm ) {
778 return $this->authForm;
779 }
780
781 $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
782
783 // get basic form description from the auth logic
784 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
785 // this will call onAuthChangeFormFields()
786 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $this->authAction );
787 $this->postProcessFormDescriptor( $formDescriptor, $requests );
788
789 $context = $this->getContext();
790 if ( $context->getRequest() !== $this->getRequest() ) {
791 // We have overridden the request, need to make sure the form uses that too.
792 $context = new DerivativeContext( $this->getContext() );
793 $context->setRequest( $this->getRequest() );
794 }
795 $form = HTMLForm::factory( 'codex', $formDescriptor, $context );
796
797 $form->addHiddenField( 'authAction', $this->authAction );
798 $form->addHiddenField( 'force', $this->securityLevel );
799 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
800 $config = $this->getConfig();
801 if ( $config->get( MainConfigNames::SecureLogin ) &&
802 !$config->get( MainConfigNames::ForceHTTPS ) ) {
803 // If using HTTPS coming from HTTP, then the 'fromhttp' parameter must be preserved
804 if ( !$this->isSignup() ) {
805 $form->addHiddenField( 'wpForceHttps', (int)$this->mStickHTTPS );
806 $form->addHiddenField( 'wpFromhttp', $usingHTTPS );
807 }
808 }
809
810 $form->setAction( $this->getPageTitle()->getLocalURL( $this->getPreservedParams(
811 // We have manually set authAction above, so we don't need it in the action URL.
812 [ 'reset' => true ]
813 ) ) );
814 $form->setName( 'userlogin' . ( $this->isSignup() ? '2' : '' ) );
815 if ( $this->isSignup() ) {
816 $form->setId( 'userlogin2' );
817 }
818
819 $form->suppressDefaultSubmit();
820
821 $this->authForm = $form;
822
823 return $form;
824 }
825
827 public function onAuthChangeFormFields(
828 array $requests, array $fieldInfo, array &$formDescriptor, $action
829 ) {
830 $formDescriptor = self::mergeDefaultFormDescriptor( $fieldInfo, $formDescriptor,
831 $this->getFieldDefinitions( $fieldInfo, $requests ) );
832 }
833
840 protected function showExtraInformation() {
841 return $this->authAction !== $this->getContinueAction( $this->authAction )
842 && !$this->securityLevel;
843 }
844
853 protected function getFieldDefinitions( array $fieldInfo, array $requests ) {
854 $isLoggedIn = $this->getUser()->isRegistered();
855 $continuePart = $this->isContinued() ? 'continue-' : '';
856 $anotherPart = $isLoggedIn ? 'another-' : '';
857 // @phan-suppress-next-line PhanUndeclaredMethod
858 $expiration = $this->getRequest()->getSession()->getProvider()->getRememberUserDuration();
859 $expirationDays = ceil( $expiration / ( 3600 * 24 ) );
860 $secureLoginLink = '';
861 if ( $this->mSecureLoginUrl ) {
862 $secureLoginLink = Html::rawElement( 'a', [
863 'href' => $this->mSecureLoginUrl,
864 'class' => 'mw-login-flush-right mw-secure',
865 ], Html::element( 'span', [ 'class' => 'mw-secure--icon' ] ) .
866 $this->msg( 'userlogin-signwithsecure' )->parse() );
867 }
868 $usernameHelpLink = '';
869 if ( !$this->msg( 'createacct-helpusername' )->isDisabled() ) {
870 $usernameHelpLink = Html::rawElement( 'span', [
871 'class' => 'mw-login-flush-right',
872 ], $this->msg( 'createacct-helpusername' )->parse() );
873 }
874
875 if ( $this->isSignup() ) {
876 $config = $this->getConfig();
877 $hideIf = isset( $fieldInfo['mailpassword'] ) ? [ 'hide-if' => [ '===', 'mailpassword', '1' ] ] : [];
878 $fieldDefinitions = [
879 'username' => [
880 'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $usernameHelpLink,
881 'id' => 'wpName2',
882 'placeholder-message' => $isLoggedIn ? 'createacct-another-username-ph'
883 : 'userlogin-yourname-ph',
884 ],
885 'mailpassword' => [
886 // create account without providing password, a temporary one will be mailed
887 'type' => 'check',
888 'label-message' => 'createaccountmail',
889 'name' => 'wpCreateaccountMail',
890 'id' => 'wpCreateaccountMail',
891 ],
892 'password' => [
893 'id' => 'wpPassword2',
894 'autocomplete' => 'new-password',
895 'placeholder-message' => 'createacct-yourpassword-ph',
896 'help-message' => 'createacct-useuniquepass',
897 ] + $hideIf,
898 'domain' => [],
899 'retype' => [
900 'type' => 'password',
901 'label-message' => 'createacct-yourpasswordagain',
902 'id' => 'wpRetype',
903 'cssclass' => 'loginPassword',
904 'size' => 20,
905 'autocomplete' => 'new-password',
906 'validation-callback' => function ( $value, $alldata ) {
907 if ( empty( $alldata['mailpassword'] ) && !empty( $alldata['password'] ) ) {
908 if ( !$value ) {
909 return $this->msg( 'htmlform-required' );
910 } elseif ( $value !== $alldata['password'] ) {
911 return $this->msg( 'badretype' );
912 }
913 }
914 return true;
915 },
916 'placeholder-message' => 'createacct-yourpasswordagain-ph',
917 ] + $hideIf,
918 'email' => [
919 'type' => 'email',
920 'label-message' => $config->get( MainConfigNames::EmailConfirmToEdit )
921 ? 'createacct-emailrequired' : 'createacct-emailoptional',
922 'id' => 'wpEmail',
923 'cssclass' => 'loginText',
924 'size' => '20',
925 'maxlength' => 255,
926 'autocomplete' => 'email',
927 // FIXME will break non-standard providers
928 'required' => $config->get( MainConfigNames::EmailConfirmToEdit ),
929 'validation-callback' => function ( $value, $alldata ) {
930 // AuthManager will check most of these, but that will make the auth
931 // session fail and this won't, so nicer to do it this way
932 if ( !$value &&
933 $this->getConfig()->get( MainConfigNames::EmailConfirmToEdit )
934 ) {
935 // no point in allowing registration without email when email is
936 // required to edit
937 return $this->msg( 'noemailtitle' );
938 } elseif ( !$value && !empty( $alldata['mailpassword'] ) ) {
939 // cannot send password via email when there is no email address
940 return $this->msg( 'noemailcreate' );
941 } elseif ( $value && !Sanitizer::validateEmail( $value ) ) {
942 return $this->msg( 'invalidemailaddress' );
943 } elseif ( is_string( $value ) && strlen( $value ) > 255 ) {
944 return $this->msg( 'changeemail-maxlength' );
945 }
946 return true;
947 },
948 // The following messages are used here:
949 // * createacct-email-ph
950 // * createacct-another-email-ph
951 'placeholder-message' => 'createacct-' . $anotherPart . 'email-ph',
952 ],
953 'realname' => [
954 'type' => 'text',
955 'help-message' => $isLoggedIn ? 'createacct-another-realname-tip'
956 : 'prefs-help-realname',
957 'label-message' => 'createacct-realname',
958 'cssclass' => 'loginText',
959 'size' => 20,
960 'placeholder-message' => 'createacct-realname',
961 'id' => 'wpRealName',
962 'autocomplete' => 'name',
963 ],
964 'reason' => [
965 // comment for the user creation log
966 'type' => 'text',
967 'label-message' => 'createacct-reason',
968 'cssclass' => 'loginText',
969 'id' => 'wpReason',
970 'size' => '20',
971 'validation-callback' => function ( $value, $alldata ) {
972 // if the user sets an email address as the user creation reason, confirm that
973 // that was their intent
974 if ( $value && Sanitizer::validateEmail( $value ) ) {
975 if ( $this->reasonValidatorResult !== null ) {
976 return $this->reasonValidatorResult;
977 }
978 $this->reasonValidatorResult = true;
979 $authManager = MediaWikiServices::getInstance()->getAuthManager();
980 if ( !$authManager->getAuthenticationSessionData( 'reason-retry', false ) ) {
981 $authManager->setAuthenticationSessionData( 'reason-retry', true );
982 $this->reasonValidatorResult = $this->msg( 'createacct-reason-confirm' );
983 }
984 return $this->reasonValidatorResult;
985 }
986 return true;
987 },
988 'placeholder-message' => 'createacct-reason-ph',
989 ],
990 'createaccount' => [
991 // submit button
992 'type' => 'submit',
993 // The following messages are used here:
994 // * createacct-submit
995 // * createacct-another-submit
996 // * createacct-continue-submit
997 // * createacct-another-continue-submit
998 'default' => $this->msg( 'createacct-' . $anotherPart . $continuePart .
999 'submit' )->text(),
1000 'name' => 'wpCreateaccount',
1001 'id' => 'wpCreateaccount',
1002 'weight' => 100,
1003 ],
1004 ];
1005 if ( !$this->msg( 'createacct-username-help' )->isDisabled() ) {
1006 $fieldDefinitions['username']['help-message'] = 'createacct-username-help';
1007 }
1008 } else {
1009 // When the user's password is too weak, they might be asked to provide a stronger one
1010 // as a followup step. That is a form with only two fields, 'password' and 'retype',
1011 // and they should behave more like account creation.
1012 $passwordRequest = AuthenticationRequest::getRequestByClass( $this->authRequests,
1013 PasswordAuthenticationRequest::class );
1014 $changePassword = $passwordRequest && $passwordRequest->action == AuthManager::ACTION_CHANGE;
1015 $fieldDefinitions = [
1016 'username' => (
1017 [
1018 'label-raw' => $this->msg( 'userlogin-yourname' )->escaped() . $secureLoginLink,
1019 'id' => 'wpName1',
1020 'placeholder-message' => 'userlogin-yourname-ph',
1021 ] + ( $changePassword ? [
1022 // There is no username field on the AuthManager level when changing
1023 // passwords. Fake one because password
1024 'baseField' => 'password',
1025 'nodata' => true,
1026 'readonly' => true,
1027 'cssclass' => 'mw-htmlform-hidden-field',
1028 ] : [] )
1029 ),
1030 'password' => (
1031 $changePassword ? [
1032 'autocomplete' => 'new-password',
1033 'placeholder-message' => 'createacct-yourpassword-ph',
1034 'help-message' => 'createacct-useuniquepass',
1035 ] : [
1036 'id' => 'wpPassword1',
1037 'autocomplete' => 'current-password',
1038 'placeholder-message' => 'userlogin-yourpassword-ph',
1039 ]
1040 ),
1041 'retype' => [
1042 'type' => 'password',
1043 'autocomplete' => 'new-password',
1044 'placeholder-message' => 'createacct-yourpasswordagain-ph',
1045 ],
1046 'domain' => [],
1047 'rememberMe' => [
1048 // option for saving the user token to a cookie
1049 'type' => 'check',
1050 'cssclass' => 'mw-userlogin-rememberme',
1051 'name' => 'wpRemember',
1052 'label-message' => $this->msg( 'userlogin-remembermypassword' )
1053 ->numParams( $expirationDays ),
1054 'id' => 'wpRemember',
1055 ],
1056 'loginattempt' => [
1057 // submit button
1058 'type' => 'submit',
1059 // The following messages are used here:
1060 // * pt-login-button
1061 // * pt-login-continue-button
1062 'default' => $this->msg( 'pt-login-' . $continuePart . 'button' )->text(),
1063 'id' => 'wpLoginAttempt',
1064 'weight' => 100,
1065 ],
1066 'linkcontainer' => [
1067 // help link
1068 'type' => 'info',
1069 'cssclass' => 'mw-form-related-link-container mw-userlogin-help',
1070 // 'id' => 'mw-userlogin-help', // FIXME HTMLInfoField ignores this
1071 'raw' => true,
1072 'default' => Html::element( 'a', [
1073 'href' => Skin::makeInternalOrExternalUrl( $this->msg( 'helplogin-url' )
1074 ->inContentLanguage()
1075 ->text() ),
1076 ], $this->msg( 'userlogin-helplink2' )->text() ),
1077 'weight' => 200,
1078 ],
1079 // button for ResetPasswordSecondaryAuthenticationProvider
1080 'skipReset' => [
1081 'weight' => 110,
1082 'flags' => [],
1083 ],
1084 ];
1085 }
1086
1087 // T369641: We want to ensure that this transformation to the username and/or
1088 // password fields are applied only when we have matching requests within the
1089 // authentication manager.
1090 $isUsernameOrPasswordRequest =
1091 AuthenticationRequest::getRequestByClass( $requests, UsernameAuthenticationRequest::class ) ||
1092 AuthenticationRequest::getRequestByClass( $requests, PasswordAuthenticationRequest::class );
1093
1094 if ( $isUsernameOrPasswordRequest ) {
1095 $fieldDefinitions['username'] += [
1096 'type' => 'text',
1097 'name' => 'wpName',
1098 'cssclass' => 'loginText mw-userlogin-username',
1099 'size' => 20,
1100 'autocomplete' => 'username',
1101 // 'required' => true,
1102 ];
1103 $fieldDefinitions['password'] += [
1104 'type' => 'password',
1105 // 'label-message' => 'userlogin-yourpassword', // would override the changepassword label
1106 'name' => 'wpPassword',
1107 'cssclass' => 'loginPassword mw-userlogin-password',
1108 'size' => 20,
1109 // 'required' => true,
1110 ];
1111 }
1112
1113 if ( $this->mEntryError ) {
1114 $defaultHtml = '';
1115 if ( $this->mEntryErrorType === 'error' ) {
1116 $defaultHtml = Html::errorBox( $this->mEntryError );
1117 } elseif ( $this->mEntryErrorType === 'warning' ) {
1118 $defaultHtml = Html::warningBox( $this->mEntryError );
1119 } elseif ( $this->mEntryErrorType === 'notice' ) {
1120 $defaultHtml = Html::noticeBox( $this->mEntryError, '' );
1121 }
1122 $fieldDefinitions['entryError'] = [
1123 'type' => 'info',
1124 'default' => $defaultHtml,
1125 'raw' => true,
1126 'rawrow' => true,
1127 'weight' => -100,
1128 ];
1129 }
1130 if ( !$this->showExtraInformation() ) {
1131 unset( $fieldDefinitions['linkcontainer'], $fieldDefinitions['signupend'] );
1132 }
1133 if ( $this->isSignup() && $this->showExtraInformation() ) {
1134 // blank signup footer for site customization
1135 // uses signupend-https for HTTPS requests if it's not blank, signupend otherwise
1136 $signupendMsg = $this->msg( 'signupend' );
1137 $signupendHttpsMsg = $this->msg( 'signupend-https' );
1138 if ( !$signupendMsg->isDisabled() ) {
1139 $usingHTTPS = $this->getRequest()->getProtocol() === 'https';
1140 $signupendText = ( $usingHTTPS && !$signupendHttpsMsg->isBlank() )
1141 ? $signupendHttpsMsg->parse() : $signupendMsg->parse();
1142 $fieldDefinitions['signupend'] = [
1143 'type' => 'info',
1144 'raw' => true,
1145 'default' => Html::rawElement( 'div', [ 'id' => 'signupend' ], $signupendText ),
1146 'weight' => 225,
1147 ];
1148 }
1149 }
1150 if ( !$this->isSignup() && $this->showExtraInformation() ) {
1151 $passwordReset = MediaWikiServices::getInstance()->getPasswordReset();
1152 if ( $passwordReset->isAllowed( $this->getUser() )->isGood() ) {
1153 $fieldDefinitions['passwordReset'] = [
1154 'type' => 'info',
1155 'raw' => true,
1156 'cssclass' => 'mw-form-related-link-container',
1157 'default' => $this->getLinkRenderer()->makeLink(
1158 SpecialPage::getTitleFor( 'PasswordReset' ),
1159 $this->msg( 'userlogin-resetpassword-link' )->text()
1160 ),
1161 'weight' => 230,
1162 ];
1163 }
1164
1165 // Don't show a "create account" link if the user can't.
1166 if ( $this->showCreateAccountLink() ) {
1167 // link to the other action
1168 $linkTitle = $this->getTitleFor( $this->isSignup() ? 'Userlogin' : 'CreateAccount' );
1169 $linkq = wfArrayToCgi( $this->getPreservedParams( [ 'reset' => true ] ) );
1170 $isLoggedIn = $this->getUser()->isRegistered()
1171 && !$this->getUser()->isTemp();
1172
1173 $fieldDefinitions['createOrLogin'] = [
1174 'type' => 'info',
1175 'raw' => true,
1176 'linkQuery' => $linkq,
1177 'default' => function ( $params ) use ( $isLoggedIn, $linkTitle ) {
1178 $buttonClasses = 'cdx-button cdx-button--action-progressive '
1179 . 'cdx-button--fake-button cdx-button--fake-button--enabled';
1180
1181 return Html::rawElement( 'div',
1182 // The following element IDs are used here:
1183 // mw-createaccount, mw-createaccount-cta
1184 [ 'id' => 'mw-createaccount' . ( !$isLoggedIn ? '-cta' : '' ),
1185 'class' => ( $isLoggedIn ? 'mw-form-related-link-container' : 'mw-ui-vform-field' ) ],
1186 ( $isLoggedIn ? '' : $this->msg( 'userlogin-noaccount' )->escaped() )
1187 . Html::element( 'a',
1188 [
1189 // The following element IDs are used here:
1190 // mw-createaccount-join, mw-createaccount-join-loggedin
1191 'id' => 'mw-createaccount-join' . ( $isLoggedIn ? '-loggedin' : '' ),
1192 'href' => $linkTitle->getLocalURL( $params['linkQuery'] ),
1193 'class' => [ 'mw-authentication-popup-link', $buttonClasses => !$isLoggedIn ],
1194 'target' => '_self',
1195 'tabindex' => 100,
1196 ],
1197 $this->msg(
1198 $isLoggedIn ? 'userlogin-createanother' : 'userlogin-joinproject'
1199 )->text()
1200 )
1201 );
1202 },
1203 'weight' => 235,
1204 ];
1205 }
1206 }
1207
1208 return $fieldDefinitions;
1209 }
1210
1220 protected function hasSessionCookie() {
1221 global $wgInitialSessionId;
1222
1223 return $wgInitialSessionId &&
1224 $this->getRequest()->getSession()->getId() === (string)$wgInitialSessionId;
1225 }
1226
1232 private function showCreateAccountLink() {
1233 return $this->isSignup() ||
1234 $this->getContext()->getAuthority()->isAllowed( 'createaccount' );
1235 }
1236
1237 protected function getTokenName() {
1238 return $this->isSignup() ? 'wpCreateaccountToken' : 'wpLoginToken';
1239 }
1240
1247 protected function makeLanguageSelector() {
1248 $msg = $this->msg( 'loginlanguagelinks' )->inContentLanguage();
1249 if ( $msg->isBlank() ) {
1250 return '';
1251 }
1252 $langs = explode( "\n", $msg->text() );
1253 $links = [];
1254 foreach ( $langs as $lang ) {
1255 $lang = trim( $lang, '* ' );
1256 $parts = explode( '|', $lang );
1257 if ( count( $parts ) >= 2 ) {
1258 $links[] = $this->makeLanguageSelectorLink( $parts[0], trim( $parts[1] ) );
1259 }
1260 }
1261
1262 return count( $links ) > 0 ? $this->msg( 'loginlanguagelabel' )->rawParams(
1263 $this->getLanguage()->pipeList( $links ) )->escaped() : '';
1264 }
1265
1274 protected function makeLanguageSelectorLink( $text, $lang ) {
1275 $services = MediaWikiServices::getInstance();
1276
1277 if ( $this->getLanguage()->getCode() == $lang
1278 || !$services->getLanguageNameUtils()->isValidCode( $lang )
1279 ) {
1280 // no link for currently used language
1281 // or invalid language code
1282 return htmlspecialchars( $text );
1283 }
1284
1285 $query = $this->getPreservedParams();
1286 $query['uselang'] = $lang;
1287
1288 $attr = [];
1289 $targetLanguage = $services->getLanguageFactory()->getLanguage( $lang );
1290 $attr['lang'] = $attr['hreflang'] = $targetLanguage->getHtmlCode();
1291 $attr['class'] = 'mw-authentication-popup-link';
1292 $attr['title'] = false;
1293
1294 return $this->getLinkRenderer()->makeKnownLink(
1295 $this->getPageTitle(),
1296 $text,
1297 $attr,
1298 $query
1299 );
1300 }
1301
1302 protected function getGroupName() {
1303 return 'login';
1304 }
1305
1310 protected function postProcessFormDescriptor( &$formDescriptor, $requests ) {
1311 // Pre-fill username (if not creating an account, T46775).
1312 if (
1313 isset( $formDescriptor['username'] ) &&
1314 !isset( $formDescriptor['username']['default'] ) &&
1315 !$this->isSignup()
1316 ) {
1317 $user = $this->getUser();
1318 if ( $user->isRegistered() && !$user->isTemp() ) {
1319 $formDescriptor['username']['default'] = $user->getName();
1320 } else {
1321 $formDescriptor['username']['default'] =
1322 $this->getRequest()->getSession()->suggestLoginUsername();
1323 }
1324 }
1325
1326 // don't show a submit button if there is nothing to submit (i.e. the only form content
1327 // is other submit buttons, for redirect flows)
1328 if ( !$this->needsSubmitButton( $requests ) ) {
1329 unset( $formDescriptor['createaccount'], $formDescriptor['loginattempt'] );
1330 }
1331
1332 if ( !$this->isSignup() ) {
1333 // FIXME HACK don't focus on non-empty field
1334 // maybe there should be an autofocus-if similar to hide-if?
1335 if (
1336 isset( $formDescriptor['username'] )
1337 && empty( $formDescriptor['username']['default'] )
1338 && !$this->getRequest()->getCheck( 'wpName' )
1339 ) {
1340 $formDescriptor['username']['autofocus'] = true;
1341 } elseif ( isset( $formDescriptor['password'] ) ) {
1342 $formDescriptor['password']['autofocus'] = true;
1343 }
1344 }
1345
1346 $this->addTabIndex( $formDescriptor );
1347 }
1348
1354 protected function getNoticeHtml() {
1355 $noticeContent = $this->msg( 'createacct-temp-warning', $this->getUser()->getName() )->parse();
1356 return Html::noticeBox(
1357 $noticeContent,
1358 'mw-createaccount-temp-warning',
1359 '',
1360 'mw-userLogin-icon--user-temporary'
1361 );
1362 }
1363
1364}
1365
1367class_alias( LoginSignupSpecialPage::class, 'LoginSignupSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:211
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
global $wgInitialSessionId
Definition Setup.php:449
array $params
The job parameters.
An error page which can definitely be safely rendered using the OutputPage.
Abort the web request with a custom HTML string that will represent the entire response.
Helper functions for the login form that need to be shared with other special pages (such as CentralA...
static getValidErrorMessages()
Returns an array of all valid error messages.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
This is a value object to hold authentication response data.
This is a value object for authentication requests with a username and password.
AuthenticationRequest to ensure something with a username is present.
An IContextSource implementation which will inherit context from another source but allow individual ...
Group all the pieces relevant to the context of a request into one instance.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
This class is a collection of static functions that serve two purposes:
Definition Html.php:56
Variant of the Message class.
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const LoginLanguageSelector
Name constant for the LoginLanguageSelector setting, for use with Config::get()
const ForceHTTPS
Name constant for the ForceHTTPS setting, for use with Config::get()
const EmailConfirmToEdit
Name constant for the EmailConfirmToEdit setting, for use with Config::get()
const SecureLogin
Name constant for the SecureLogin setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:155
HTML sanitizer for MediaWiki.
Definition Sanitizer.php:46
This serves as the entry point to the MediaWiki session handling system.
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
isContinued()
Returns true if this is not the first step of the authentication.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
trySubmit()
Attempts to do an authentication step with the submitted data.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
getDefaultAction( $subPage)
Get the default action for this special page if none is given via URL/POST data.
getRequest()
Get the WebRequest being used for this instance.
Holds shared logic for login and account creation pages.
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
makeLanguageSelectorLink( $text, $lang)
Create a language selector link for a particular language Links back to this page preserving type and...
string string $mReturnToAnchor
The fragment part of the URL to return to after authentication finishes.
beforeExecute( $subPage)
Gets called before.SpecialPage::execute. Return false to prevent calling execute() (since 1....
showSuccessPage( $type, $title, $msgname, $injected_html, $extraMessages)
Show the success page.
getAuthForm(array $requests, $action)
Generates a form from the given request.
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
getBenefitsContainerHtml()
The HTML to be shown in the "benefits to signing in / creating an account" section of the signup/logi...
mainLoginForm(array $requests, $msg='', $msgtype='error')
getPreservedParams( $options=[])
Returns URL query parameters which should be preserved between authentication requests....
logAuthResult( $success, UserIdentity $performer, $status=null)
Logs to the authmanager-stats channel.
bool $proxyAccountCreation
True if the user if creating an account for someone else.
showExtraInformation()
Show extra information such as password recovery information, link from login to signup,...
string string $mReturnToQuery
The query string part of the URL to return to after authentication finishes.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form....
makeLanguageSelector()
Produce a bar of links which allow the user to select another language during login/registration but ...
successfulAction( $direct=false, $extraMessages=null)
string string $mReturnTo
The title of the page to return to after authentication finishes, or the empty string when there is n...
getNoticeHtml()
Generates the HTML for a notice box to be displayed to a temporary user.
hasSessionCookie()
Check if a session cookie is present.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getFieldDefinitions(array $fieldInfo, array $requests)
Create a HTMLForm descriptor for the core login fields.
getPageHtml( $formHtml)
Add page elements which are outside the form.
User $targetUser
FIXME another flag for passing data.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getUser()
Shortcut to get the User executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
setContext( $context)
Sets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getOutput()
Get the OutputPage being used for this instance.
getName()
Get the canonical, unlocalized name of this special page without namespace.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
User class for the MediaWiki software.
Definition User.php:119
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
The base class for all skins.
Definition Skin.php:64
static makeInternalOrExternalUrl( $name)
If url string starts with http, consider as external URL, else internal.
Definition Skin.php:1205
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for objects representing user identity.
element(SerializerNode $parent, SerializerNode $node, $contents)