MediaWiki master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
4
6use HTMLInfoField;
7use InvalidArgumentException;
8use LogicException;
21use MWCryptRand;
22use StatusValue;
23use UnexpectedValueException;
24
35abstract class AuthManagerSpecialPage extends SpecialPage {
39 protected static $allowedActions = [
40 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
41 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
42 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
43 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
44 ];
45
47 protected static $messages = [];
48
50 protected $authAction;
51
53 protected $authRequests;
54
56 protected $subPage;
57
59 protected $isReturn;
60
62 protected $savedRequest;
63
76 public function onAuthChangeFormFields(
77 array $requests, array $fieldInfo, array &$formDescriptor, $action
78 ) {
79 }
80
85 protected function getLoginSecurityLevel() {
86 return $this->getName();
87 }
88
89 public function getRequest() {
90 return $this->savedRequest ?: $this->getContext()->getRequest();
91 }
92
103 protected function setRequest( array $data, $wasPosted = null ) {
104 $request = $this->getContext()->getRequest();
105 $this->savedRequest = new DerivativeRequest(
106 $request,
107 $data + $request->getQueryValues(),
108 $wasPosted ?? $request->wasPosted()
109 );
110 }
111
118 protected function beforeExecute( $subPage ) {
119 $this->getOutput()->disallowUserJs();
120
121 return $this->handleReturnBeforeExecute( $subPage )
123 }
124
141 protected function handleReturnBeforeExecute( $subPage ) {
142 $authManager = $this->getAuthManager();
143 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
144
145 if ( $subPage === 'return' ) {
146 $this->loadAuth( $subPage );
147 $preservedParams = $this->getPreservedParams( false );
148
149 // FIXME save POST values only from request
150 $authData = array_diff_key( $this->getRequest()->getValues(),
151 $preservedParams, [ 'title' => 1 ] );
152 $authManager->setAuthenticationSessionData( $key, $authData );
153
154 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
155 $this->getOutput()->redirect( $url );
156 return false;
157 }
158
159 $authData = $authManager->getAuthenticationSessionData( $key );
160 if ( $authData ) {
161 $authManager->removeAuthenticationSessionData( $key );
162 $this->isReturn = true;
163 $this->setRequest( $authData, true );
164 }
165
166 return true;
167 }
168
179 protected function handleReauthBeforeExecute( $subPage ) {
180 $authManager = $this->getAuthManager();
181 $request = $this->getRequest();
182 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
183
184 $securityLevel = $this->getLoginSecurityLevel();
185 if ( $securityLevel ) {
186 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
187 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
188 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
189
190 if ( $request->wasPosted() ) {
191 // unique ID in case the same special page is open in multiple browser tabs
192 $uniqueId = MWCryptRand::generateHex( 6 );
193 $key .= ':' . $uniqueId;
194
195 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
196 $authData = array_diff_key( $request->getValues(),
197 $this->getPreservedParams( false ), [ 'title' => 1 ] );
198 $authManager->setAuthenticationSessionData( $key, $authData );
199 }
200
201 $title = SpecialPage::getTitleFor( 'Userlogin' );
202 $url = $title->getFullURL( [
203 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
204 'returntoquery' => wfArrayToCgi( $queryParams ),
205 'force' => $securityLevel,
206 ], false, PROTO_HTTPS );
207
208 $this->getOutput()->redirect( $url );
209 return false;
210 }
211
212 if ( $securityStatus !== AuthManager::SEC_OK ) {
213 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
214 }
215 }
216
217 $uniqueId = $request->getVal( 'authUniqueId' );
218 if ( $uniqueId ) {
219 $key .= ':' . $uniqueId;
220 $authData = $authManager->getAuthenticationSessionData( $key );
221 if ( $authData ) {
222 $authManager->removeAuthenticationSessionData( $key );
223 $this->setRequest( $authData, true );
224 }
225 }
226
227 return true;
228 }
229
237 abstract protected function getDefaultAction( $subPage );
238
245 protected function messageKey( $defaultKey ) {
246 return array_key_exists( $defaultKey, static::$messages )
247 ? static::$messages[$defaultKey] : $defaultKey;
248 }
249
255 protected function getRequestBlacklist() {
256 return [];
257 }
258
269 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
270 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
271 // calls. This is important for requests which have hidden information, so any
272 // getAuthenticationRequests call would mean putting data into some cache.
273 if (
274 !$reset && $this->subPage === $subPage && $this->authAction
275 && ( !$authAction || $authAction === $this->authAction )
276 ) {
277 return;
278 }
279
280 $request = $this->getRequest();
281 $this->subPage = $subPage;
282 $this->authAction = $authAction ?: $request->getText( 'authAction' );
283 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
284 $this->authAction = $this->getDefaultAction( $subPage );
285 if ( $request->wasPosted() ) {
286 $continueAction = $this->getContinueAction( $this->authAction );
287 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
288 $this->authAction = $continueAction;
289 }
290 }
291 }
292
293 $allReqs = $this->getAuthManager()->getAuthenticationRequests(
294 $this->authAction, $this->getUser() );
295 $this->authRequests = array_filter( $allReqs, function ( $req ) {
296 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
297 } );
298 }
299
304 protected function isContinued() {
305 return in_array( $this->authAction, [
306 AuthManager::ACTION_LOGIN_CONTINUE,
307 AuthManager::ACTION_CREATE_CONTINUE,
308 AuthManager::ACTION_LINK_CONTINUE,
309 ], true );
310 }
311
317 protected function getContinueAction( $action ) {
318 switch ( $action ) {
319 case AuthManager::ACTION_LOGIN:
320 $action = AuthManager::ACTION_LOGIN_CONTINUE;
321 break;
322 case AuthManager::ACTION_CREATE:
323 $action = AuthManager::ACTION_CREATE_CONTINUE;
324 break;
325 case AuthManager::ACTION_LINK:
326 $action = AuthManager::ACTION_LINK_CONTINUE;
327 break;
328 }
329 return $action;
330 }
331
340 protected function isActionAllowed( $action ) {
341 $authManager = $this->getAuthManager();
342 if ( !in_array( $action, static::$allowedActions, true ) ) {
343 throw new InvalidArgumentException( 'invalid action: ' . $action );
344 }
345
346 // calling getAuthenticationRequests can be expensive, avoid if possible
347 $requests = ( $action === $this->authAction ) ? $this->authRequests
348 : $authManager->getAuthenticationRequests( $action );
349 if ( !$requests ) {
350 // no provider supports this action in the current state
351 return false;
352 }
353
354 switch ( $action ) {
355 case AuthManager::ACTION_LOGIN:
356 case AuthManager::ACTION_LOGIN_CONTINUE:
357 return $authManager->canAuthenticateNow();
358 case AuthManager::ACTION_CREATE:
359 case AuthManager::ACTION_CREATE_CONTINUE:
360 return $authManager->canCreateAccounts();
361 case AuthManager::ACTION_LINK:
362 case AuthManager::ACTION_LINK_CONTINUE:
363 return $authManager->canLinkAccounts();
364 case AuthManager::ACTION_CHANGE:
365 case AuthManager::ACTION_REMOVE:
366 case AuthManager::ACTION_UNLINK:
367 return true;
368 default:
369 // should never reach here but makes static code analyzers happy
370 throw new InvalidArgumentException( 'invalid action: ' . $action );
371 }
372 }
373
380 protected function performAuthenticationStep( $action, array $requests ) {
381 if ( !in_array( $action, static::$allowedActions, true ) ) {
382 throw new InvalidArgumentException( 'invalid action: ' . $action );
383 }
384
385 $authManager = $this->getAuthManager();
386 $returnToUrl = $this->getPageTitle( 'return' )
387 ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
388
389 switch ( $action ) {
390 case AuthManager::ACTION_LOGIN:
391 return $authManager->beginAuthentication( $requests, $returnToUrl );
392 case AuthManager::ACTION_LOGIN_CONTINUE:
393 return $authManager->continueAuthentication( $requests );
394 case AuthManager::ACTION_CREATE:
395 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
396 $returnToUrl );
397 case AuthManager::ACTION_CREATE_CONTINUE:
398 return $authManager->continueAccountCreation( $requests );
399 case AuthManager::ACTION_LINK:
400 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
401 case AuthManager::ACTION_LINK_CONTINUE:
402 return $authManager->continueAccountLink( $requests );
403 case AuthManager::ACTION_CHANGE:
404 case AuthManager::ACTION_REMOVE:
405 case AuthManager::ACTION_UNLINK:
406 if ( count( $requests ) > 1 ) {
407 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
408 }
409
410 if ( !$requests ) {
411 throw new InvalidArgumentException( 'no auth request' );
412 }
413 $req = reset( $requests );
414 $status = $authManager->allowsAuthenticationDataChange( $req );
415 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
416 if ( !$status->isGood() ) {
417 return AuthenticationResponse::newFail( $status->getMessage() );
418 }
419 $authManager->changeAuthenticationData( $req );
420 return AuthenticationResponse::newPass();
421 default:
422 // should never reach here but makes static code analyzers happy
423 throw new InvalidArgumentException( 'invalid action: ' . $action );
424 }
425 }
426
437 protected function trySubmit() {
438 $status = false;
439
440 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
441 $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
442
443 if ( $this->getRequest()->wasPosted() ) {
444 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
445 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
446 $sessionToken = $this->getToken();
447 if ( $sessionToken->wasNew() ) {
448 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
449 } elseif ( !$requestTokenValue ) {
450 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
451 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
452 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
453 }
454
455 $form->prepareForm();
456 $status = $form->trySubmit();
457
458 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
459 // FIXME this probably should be in HTMLForm
460 if ( $status === true ) {
461 // not supposed to happen since our submit handler should always return a Status
462 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
463 } elseif ( $status === false ) {
464 // form was not submitted; nothing to do
465 } elseif ( $status instanceof Status ) {
466 // already handled by the form; nothing to do
467 } elseif ( $status instanceof StatusValue ) {
468 // in theory not an allowed return type but nothing stops the submit handler from
469 // accidentally returning it so best check and fix
470 $status = Status::wrap( $status );
471 } elseif ( is_string( $status ) ) {
472 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
473 } elseif ( is_array( $status ) ) {
474 if ( is_string( reset( $status ) ) ) {
475 // @phan-suppress-next-line PhanParamTooFewUnpack
476 $status = Status::newFatal( ...$status );
477 } elseif ( is_array( reset( $status ) ) ) {
478 $ret = Status::newGood();
479 foreach ( $status as $message ) {
480 // @phan-suppress-next-line PhanParamTooFewUnpack
481 $ret->fatal( ...$message );
482 }
483 $status = $ret;
484 } else {
485 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
486 . 'first element of array is ' . gettype( reset( $status ) ) );
487 }
488 } else {
489 // not supposed to happen, but HTMLForm does not verify the return type
490 // from the submit callback; better safe then sorry!
491 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
492 . gettype( $status ) );
493 }
494
495 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
496 // This is awkward. There was a form validation error, which means the data was not
497 // passed to AuthManager. Normally we would display the form with an error message,
498 // but for the data we received via the redirect flow that would not be helpful at all.
499 // Let's just submit the data to AuthManager directly instead.
500 LoggerFactory::getInstance( 'authentication' )
501 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
502 'status' => $status->getWikiText( false, false, 'en' ) ] );
503 $status = $this->handleFormSubmit( $form->mFieldData );
504 }
505 }
506
507 $changeActions = [
508 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
509 ];
510 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
511 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
512 }
513
514 return $status;
515 }
516
523 public function handleFormSubmit( $data ) {
524 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
525 $response = $this->performAuthenticationStep( $this->authAction, $requests );
526
527 // we can't handle FAIL or similar as failure here since it might require changing the form
528 return Status::newGood( $response );
529 }
530
539 protected function getPreservedParams( $withToken = false ) {
540 $params = [];
541 if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
542 $params['authAction'] = $this->getContinueAction( $this->authAction );
543 }
544 if ( $withToken ) {
545 $params[$this->getTokenName()] = $this->getToken()->toString();
546 }
547 return $params;
548 }
549
557 protected function getAuthFormDescriptor( $requests, $action ) {
558 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
559 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
560
561 $this->addTabIndex( $formDescriptor );
562
563 return $formDescriptor;
564 }
565
572 protected function getAuthForm( array $requests, $action ) {
573 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
574 $context = $this->getContext();
575 if ( $context->getRequest() !== $this->getRequest() ) {
576 // We have overridden the request, need to make sure the form uses that too.
577 $context = new DerivativeContext( $this->getContext() );
578 $context->setRequest( $this->getRequest() );
579 }
580 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
581 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
582 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
583 $form->addHiddenField( 'authAction', $this->authAction );
584 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
585
586 return $form;
587 }
588
593 protected function displayForm( $status ) {
594 if ( $status instanceof StatusValue ) {
595 $status = Status::wrap( $status );
596 }
597 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
598 $form->prepareForm()->displayForm( $status );
599 }
600
612 protected function needsSubmitButton( array $requests ) {
613 $customSubmitButtonPresent = false;
614
615 // Secondary and preauth providers always need their data; they will not care what button
616 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
617 // that's the point in being optional. Se we need to check whether all primary providers
618 // have their own buttons and whether there is at least one button present.
619 foreach ( $requests as $req ) {
620 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
621 if ( $this->hasOwnSubmitButton( $req ) ) {
622 $customSubmitButtonPresent = true;
623 } else {
624 return true;
625 }
626 }
627 }
628 return !$customSubmitButtonPresent;
629 }
630
636 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
637 foreach ( $req->getFieldInfo() as $info ) {
638 if ( $info['type'] === 'button' ) {
639 return true;
640 }
641 }
642 return false;
643 }
644
650 protected function addTabIndex( &$formDescriptor ) {
651 $i = 1;
652 foreach ( $formDescriptor as &$definition ) {
653 $class = false;
654 if ( array_key_exists( 'class', $definition ) ) {
655 $class = $definition['class'];
656 } elseif ( array_key_exists( 'type', $definition ) ) {
657 $class = HTMLForm::$typeMappings[$definition['type']];
658 }
659 if ( $class !== HTMLInfoField::class ) {
660 $definition['tabindex'] = $i;
661 $i++;
662 }
663 }
664 }
665
671 protected function getToken() {
672 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
673 . $this->getName() );
674 }
675
681 protected function getTokenName() {
682 return 'wpAuthToken';
683 }
684
694 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
695 $formDescriptor = [];
696 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
697 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
698 }
699
700 $requestSnapshot = serialize( $requests );
701 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
702 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
703 $formDescriptor, $action );
704 if ( $requestSnapshot !== serialize( $requests ) ) {
705 LoggerFactory::getInstance( 'authentication' )->warning(
706 'AuthChangeFormFields hook changed auth requests' );
707 }
708
709 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
710 // subscribers (who only see one field at a time) to influence ordering.
711 self::sortFormDescriptorFields( $formDescriptor );
712
713 return $formDescriptor;
714 }
715
723 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
724 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
725 $descriptor = [
726 'type' => $type,
727 // Do not prefix input name with 'wp'. This is important for the redirect flow.
728 'name' => $fieldName,
729 ];
730
731 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
732 $descriptor['default'] = $singleFieldInfo['label']->plain();
733 } elseif ( $type !== 'submit' ) {
734 $descriptor += array_filter( [
735 // help-message is omitted as it is usually not really useful for a web interface
736 'label-message' => self::getField( $singleFieldInfo, 'label' ),
737 ] );
738
739 if ( isset( $singleFieldInfo['options'] ) ) {
740 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
742 return $message->parse();
743 }, $singleFieldInfo['options'] ) );
744 }
745
746 if ( isset( $singleFieldInfo['value'] ) ) {
747 $descriptor['default'] = $singleFieldInfo['value'];
748 }
749
750 if ( empty( $singleFieldInfo['optional'] ) ) {
751 $descriptor['required'] = true;
752 }
753 }
754
755 return $descriptor;
756 }
757
764 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
765 $i = 0;
766 foreach ( $formDescriptor as &$field ) {
767 $field['__index'] = $i++;
768 }
769 unset( $field );
770 uasort( $formDescriptor, static function ( $first, $second ) {
771 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
772 ?: $first['__index'] <=> $second['__index'];
773 } );
774 foreach ( $formDescriptor as &$field ) {
775 unset( $field['__index'] );
776 }
777 }
778
786 protected static function getField( array $array, $fieldName, $default = null ) {
787 if ( array_key_exists( $fieldName, $array ) ) {
788 return $array[$fieldName];
789 } else {
790 return $default;
791 }
792 }
793
801 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
802 $map = [
803 'string' => 'text',
804 'password' => 'password',
805 'select' => 'select',
806 'checkbox' => 'check',
807 'multiselect' => 'multiselect',
808 'button' => 'submit',
809 'hidden' => 'hidden',
810 'null' => 'info',
811 ];
812 if ( !array_key_exists( $type, $map ) ) {
813 throw new InvalidArgumentException( 'invalid field type: ' . $type );
814 }
815 return $map[$type];
816 }
817
830 protected static function mergeDefaultFormDescriptor(
831 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
832 ) {
833 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
834 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
835 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
836 // to something in the fieldinfo, and is not an info field or a submit button
837 if (
838 !isset( $fieldInfo[$fieldName] )
839 && (
840 !isset( $defaultField['baseField'] )
841 || !isset( $fieldInfo[$defaultField['baseField']] )
842 )
843 && (
844 !isset( $defaultField['type'] )
845 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
846 )
847 ) {
848 $defaultFormDescriptor[$fieldName] = null;
849 continue;
850 }
851
852 // default message labels should always take priority
853 $requestField = $formDescriptor[$fieldName] ?? [];
854 if (
855 isset( $defaultField['label'] )
856 || isset( $defaultField['label-message'] )
857 || isset( $defaultField['label-raw'] )
858 ) {
859 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
860 }
861
862 $defaultFormDescriptor[$fieldName] += $requestField;
863 }
864
865 return array_filter( $defaultFormDescriptor + $formDescriptor );
866 }
867}
868
870class_alias( AuthManagerSpecialPage::class, 'AuthManagerSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:204
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
array $params
The job parameters.
An error page which can definitely be safely rendered using the OutputPage.
This serves as the entry point to the authentication system.
This is a value object for authentication requests.
getFieldInfo()
Fetch input field info.
This is a value object to hold authentication response data.
An IContextSource implementation which will inherit context from another source but allow individual ...
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:206
Variant of the Message class.
Create PSR-3 logger objects.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:158
Similar to MediaWiki\Request\FauxRequest, but only fakes URL parameters and method (POST or GET) and ...
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
Value object representing a CSRF token.
Definition Token.php:32
A special page subclass for authentication-related special pages.
bool $isReturn
True if the current request is a result of returning from a redirect flow.
handleReturnBeforeExecute( $subPage)
Handle redirection from the /return subpage.
static mapFieldInfoTypeToFormDescriptorType( $type)
Maps AuthenticationRequest::getFieldInfo() types to HTMLForm types.
getAuthFormDescriptor( $requests, $action)
Generates a HTMLForm descriptor array from a set of authentication requests.
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
static getField(array $array, $fieldName, $default=null)
Get an array value, or a default if it does not exist.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
static sortFormDescriptorFields(array &$formDescriptor)
Sort the fields of a form descriptor by their 'weight' property.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form.
static mapSingleFieldInfo( $singleFieldInfo, $fieldName)
Maps an authentication field configuration for a single field (as returned by AuthenticationRequest::...
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
isContinued()
Returns true if this is not the first step of the authentication.
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
string $authAction
one of the AuthManager::ACTION_* constants.
messageKey( $defaultKey)
Return custom message key.
static string[] $allowedActions
The list of actions this special page deals with.
static mergeDefaultFormDescriptor(array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor)
Apply defaults to a form descriptor, without creating non-existent fields.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
getRequestBlacklist()
Allows blacklisting certain request types.
trySubmit()
Attempts to do an authentication step with the submitted data.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
fieldInfoToFormDescriptor(array $requests, array $fieldInfo, $action)
Turns a field info array into a form descriptor.
handleFormSubmit( $data)
Submit handler callback for HTMLForm.
getDefaultAction( $subPage)
Get the default action for this special page if none is given via URL/POST data.
hasOwnSubmitButton(AuthenticationRequest $req)
Checks whether the given AuthenticationRequest has its own submit button.
getRequest()
Get the WebRequest being used for this instance.
WebRequest null $savedRequest
If set, will be used instead of the real request.
Parent class for all special pages.
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.
getContext()
Gets the context this SpecialPage is executed in.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing 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
Generic operation result class Has warning/error list, boolean status and arbitrary value.