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