MediaWiki 1.41.2
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
4
7use HTMLForm;
9use InvalidArgumentException;
10use LogicException;
20use MWCryptRand;
21use StatusValue;
22use UnexpectedValueException;
23
33abstract class AuthManagerSpecialPage extends SpecialPage {
37 protected static $allowedActions = [
38 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
39 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
40 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
41 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
42 ];
43
45 protected static $messages = [];
46
48 protected $authAction;
49
51 protected $authRequests;
52
54 protected $subPage;
55
57 protected $isReturn;
58
60 protected $savedRequest;
61
74 public function onAuthChangeFormFields(
75 array $requests, array $fieldInfo, array &$formDescriptor, $action
76 ) {
77 }
78
83 protected function getLoginSecurityLevel() {
84 return $this->getName();
85 }
86
87 public function getRequest() {
88 return $this->savedRequest ?: $this->getContext()->getRequest();
89 }
90
101 protected function setRequest( array $data, $wasPosted = null ) {
102 $request = $this->getContext()->getRequest();
103 $this->savedRequest = new DerivativeRequest(
104 $request,
105 $data + $request->getQueryValues(),
106 $wasPosted ?? $request->wasPosted()
107 );
108 }
109
116 protected function beforeExecute( $subPage ) {
117 $this->getOutput()->disallowUserJs();
118
119 return $this->handleReturnBeforeExecute( $subPage )
121 }
122
139 protected function handleReturnBeforeExecute( $subPage ) {
140 $authManager = $this->getAuthManager();
141 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
142
143 if ( $subPage === 'return' ) {
144 $this->loadAuth( $subPage );
145 $preservedParams = $this->getPreservedParams( false );
146
147 // FIXME save POST values only from request
148 $authData = array_diff_key( $this->getRequest()->getValues(),
149 $preservedParams, [ 'title' => 1 ] );
150 $authManager->setAuthenticationSessionData( $key, $authData );
151
152 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
153 $this->getOutput()->redirect( $url );
154 return false;
155 }
156
157 $authData = $authManager->getAuthenticationSessionData( $key );
158 if ( $authData ) {
159 $authManager->removeAuthenticationSessionData( $key );
160 $this->isReturn = true;
161 $this->setRequest( $authData, true );
162 }
163
164 return true;
165 }
166
177 protected function handleReauthBeforeExecute( $subPage ) {
178 $authManager = $this->getAuthManager();
179 $request = $this->getRequest();
180 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
181
182 $securityLevel = $this->getLoginSecurityLevel();
183 if ( $securityLevel ) {
184 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
185 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
186 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
187
188 if ( $request->wasPosted() ) {
189 // unique ID in case the same special page is open in multiple browser tabs
190 $uniqueId = MWCryptRand::generateHex( 6 );
191 $key .= ':' . $uniqueId;
192
193 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
194 $authData = array_diff_key( $request->getValues(),
195 $this->getPreservedParams( false ), [ 'title' => 1 ] );
196 $authManager->setAuthenticationSessionData( $key, $authData );
197 }
198
199 $title = SpecialPage::getTitleFor( 'Userlogin' );
200 $url = $title->getFullURL( [
201 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
202 'returntoquery' => wfArrayToCgi( $queryParams ),
203 'force' => $securityLevel,
204 ], false, PROTO_HTTPS );
205
206 $this->getOutput()->redirect( $url );
207 return false;
208 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
209 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
210 }
211 }
212
213 $uniqueId = $request->getVal( 'authUniqueId' );
214 if ( $uniqueId ) {
215 $key .= ':' . $uniqueId;
216 $authData = $authManager->getAuthenticationSessionData( $key );
217 if ( $authData ) {
218 $authManager->removeAuthenticationSessionData( $key );
219 $this->setRequest( $authData, true );
220 }
221 }
222
223 return true;
224 }
225
233 abstract protected function getDefaultAction( $subPage );
234
241 protected function messageKey( $defaultKey ) {
242 return array_key_exists( $defaultKey, static::$messages )
243 ? static::$messages[$defaultKey] : $defaultKey;
244 }
245
251 protected function getRequestBlacklist() {
252 return [];
253 }
254
265 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
266 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
267 // calls. This is important for requests which have hidden information so any
268 // getAuthenticationRequests call would mean putting data into some cache.
269 if (
270 !$reset && $this->subPage === $subPage && $this->authAction
271 && ( !$authAction || $authAction === $this->authAction )
272 ) {
273 return;
274 }
275
276 $request = $this->getRequest();
277 $this->subPage = $subPage;
278 $this->authAction = $authAction ?: $request->getText( 'authAction' );
279 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
280 $this->authAction = $this->getDefaultAction( $subPage );
281 if ( $request->wasPosted() ) {
282 $continueAction = $this->getContinueAction( $this->authAction );
283 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
284 $this->authAction = $continueAction;
285 }
286 }
287 }
288
289 $allReqs = $this->getAuthManager()->getAuthenticationRequests(
290 $this->authAction, $this->getUser() );
291 $this->authRequests = array_filter( $allReqs, function ( $req ) {
292 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
293 } );
294 }
295
300 protected function isContinued() {
301 return in_array( $this->authAction, [
302 AuthManager::ACTION_LOGIN_CONTINUE,
303 AuthManager::ACTION_CREATE_CONTINUE,
304 AuthManager::ACTION_LINK_CONTINUE,
305 ], true );
306 }
307
313 protected function getContinueAction( $action ) {
314 switch ( $action ) {
315 case AuthManager::ACTION_LOGIN:
316 $action = AuthManager::ACTION_LOGIN_CONTINUE;
317 break;
318 case AuthManager::ACTION_CREATE:
319 $action = AuthManager::ACTION_CREATE_CONTINUE;
320 break;
321 case AuthManager::ACTION_LINK:
322 $action = AuthManager::ACTION_LINK_CONTINUE;
323 break;
324 }
325 return $action;
326 }
327
336 protected function isActionAllowed( $action ) {
337 $authManager = $this->getAuthManager();
338 if ( !in_array( $action, static::$allowedActions, true ) ) {
339 throw new InvalidArgumentException( 'invalid action: ' . $action );
340 }
341
342 // calling getAuthenticationRequests can be expensive, avoid if possible
343 $requests = ( $action === $this->authAction ) ? $this->authRequests
344 : $authManager->getAuthenticationRequests( $action );
345 if ( !$requests ) {
346 // no provider supports this action in the current state
347 return false;
348 }
349
350 switch ( $action ) {
351 case AuthManager::ACTION_LOGIN:
352 case AuthManager::ACTION_LOGIN_CONTINUE:
353 return $authManager->canAuthenticateNow();
354 case AuthManager::ACTION_CREATE:
355 case AuthManager::ACTION_CREATE_CONTINUE:
356 return $authManager->canCreateAccounts();
357 case AuthManager::ACTION_LINK:
358 case AuthManager::ACTION_LINK_CONTINUE:
359 return $authManager->canLinkAccounts();
360 case AuthManager::ACTION_CHANGE:
361 case AuthManager::ACTION_REMOVE:
362 case AuthManager::ACTION_UNLINK:
363 return true;
364 default:
365 // should never reach here but makes static code analyzers happy
366 throw new InvalidArgumentException( 'invalid action: ' . $action );
367 }
368 }
369
376 protected function performAuthenticationStep( $action, array $requests ) {
377 if ( !in_array( $action, static::$allowedActions, true ) ) {
378 throw new InvalidArgumentException( 'invalid action: ' . $action );
379 }
380
381 $authManager = $this->getAuthManager();
382 $returnToUrl = $this->getPageTitle( 'return' )
383 ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
384
385 switch ( $action ) {
386 case AuthManager::ACTION_LOGIN:
387 return $authManager->beginAuthentication( $requests, $returnToUrl );
388 case AuthManager::ACTION_LOGIN_CONTINUE:
389 return $authManager->continueAuthentication( $requests );
390 case AuthManager::ACTION_CREATE:
391 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
392 $returnToUrl );
393 case AuthManager::ACTION_CREATE_CONTINUE:
394 return $authManager->continueAccountCreation( $requests );
395 case AuthManager::ACTION_LINK:
396 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
397 case AuthManager::ACTION_LINK_CONTINUE:
398 return $authManager->continueAccountLink( $requests );
399 case AuthManager::ACTION_CHANGE:
400 case AuthManager::ACTION_REMOVE:
401 case AuthManager::ACTION_UNLINK:
402 if ( count( $requests ) > 1 ) {
403 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
404 } elseif ( !$requests ) {
405 throw new InvalidArgumentException( 'no auth request' );
406 }
407 $req = reset( $requests );
408 $status = $authManager->allowsAuthenticationDataChange( $req );
409 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
410 if ( !$status->isGood() ) {
411 return AuthenticationResponse::newFail( $status->getMessage() );
412 }
413 $authManager->changeAuthenticationData( $req );
414 return AuthenticationResponse::newPass();
415 default:
416 // should never reach here but makes static code analyzers happy
417 throw new InvalidArgumentException( 'invalid action: ' . $action );
418 }
419 }
420
431 protected function trySubmit() {
432 $status = false;
433
434 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
435 $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
436
437 if ( $this->getRequest()->wasPosted() ) {
438 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
439 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
440 $sessionToken = $this->getToken();
441 if ( $sessionToken->wasNew() ) {
442 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
443 } elseif ( !$requestTokenValue ) {
444 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
445 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
446 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
447 }
448
449 $form->prepareForm();
450 $status = $form->trySubmit();
451
452 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
453 // FIXME this probably should be in HTMLForm
454 if ( $status === true ) {
455 // not supposed to happen since our submit handler should always return a Status
456 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
457 } elseif ( $status === false ) {
458 // form was not submitted; nothing to do
459 } elseif ( $status instanceof Status ) {
460 // already handled by the form; nothing to do
461 } elseif ( $status instanceof StatusValue ) {
462 // in theory not an allowed return type but nothing stops the submit handler from
463 // accidentally returning it so best check and fix
464 $status = Status::wrap( $status );
465 } elseif ( is_string( $status ) ) {
466 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
467 } elseif ( is_array( $status ) ) {
468 if ( is_string( reset( $status ) ) ) {
469 // @phan-suppress-next-line PhanParamTooFewUnpack
470 $status = Status::newFatal( ...$status );
471 } elseif ( is_array( reset( $status ) ) ) {
472 $ret = Status::newGood();
473 foreach ( $status as $message ) {
474 // @phan-suppress-next-line PhanParamTooFewUnpack
475 $ret->fatal( ...$message );
476 }
477 $status = $ret;
478 } else {
479 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
480 . 'first element of array is ' . gettype( reset( $status ) ) );
481 }
482 } else {
483 // not supposed to happen but HTMLForm does not actually verify the return type
484 // from the submit callback; better safe then sorry
485 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
486 . gettype( $status ) );
487 }
488
489 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
490 // This is awkward. There was a form validation error, which means the data was not
491 // passed to AuthManager. Normally we would display the form with an error message,
492 // but for the data we received via the redirect flow that would not be helpful at all.
493 // Let's just submit the data to AuthManager directly instead.
494 LoggerFactory::getInstance( 'authentication' )
495 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
496 'status' => $status->getWikiText( false, false, 'en' ) ] );
497 $status = $this->handleFormSubmit( $form->mFieldData );
498 }
499 }
500
501 $changeActions = [
502 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
503 ];
504 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
505 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
506 }
507
508 return $status;
509 }
510
517 public function handleFormSubmit( $data ) {
518 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
519 $response = $this->performAuthenticationStep( $this->authAction, $requests );
520
521 // we can't handle FAIL or similar as failure here since it might require changing the form
522 return Status::newGood( $response );
523 }
524
533 protected function getPreservedParams( $withToken = false ) {
534 $params = [];
535 if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
536 $params['authAction'] = $this->getContinueAction( $this->authAction );
537 }
538 if ( $withToken ) {
539 $params[$this->getTokenName()] = $this->getToken()->toString();
540 }
541 return $params;
542 }
543
551 protected function getAuthFormDescriptor( $requests, $action ) {
552 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
553 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
554
555 $this->addTabIndex( $formDescriptor );
556
557 return $formDescriptor;
558 }
559
566 protected function getAuthForm( array $requests, $action ) {
567 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
568 $context = $this->getContext();
569 if ( $context->getRequest() !== $this->getRequest() ) {
570 // We have overridden the request, need to make sure the form uses that too.
571 $context = new DerivativeContext( $this->getContext() );
572 $context->setRequest( $this->getRequest() );
573 }
574 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
575 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
576 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
577 $form->addHiddenField( 'authAction', $this->authAction );
578 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
579
580 return $form;
581 }
582
587 protected function displayForm( $status ) {
588 if ( $status instanceof StatusValue ) {
589 $status = Status::wrap( $status );
590 }
591 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
592 $form->prepareForm()->displayForm( $status );
593 }
594
606 protected function needsSubmitButton( array $requests ) {
607 $customSubmitButtonPresent = false;
608
609 // Secondary and preauth providers always need their data; they will not care what button
610 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
611 // that's the point in being optional. Se we need to check whether all primary providers
612 // have their own buttons and whether there is at least one button present.
613 foreach ( $requests as $req ) {
614 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
615 if ( $this->hasOwnSubmitButton( $req ) ) {
616 $customSubmitButtonPresent = true;
617 } else {
618 return true;
619 }
620 }
621 }
622 return !$customSubmitButtonPresent;
623 }
624
630 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
631 foreach ( $req->getFieldInfo() as $info ) {
632 if ( $info['type'] === 'button' ) {
633 return true;
634 }
635 }
636 return false;
637 }
638
644 protected function addTabIndex( &$formDescriptor ) {
645 $i = 1;
646 foreach ( $formDescriptor as &$definition ) {
647 $class = false;
648 if ( array_key_exists( 'class', $definition ) ) {
649 $class = $definition['class'];
650 } elseif ( array_key_exists( 'type', $definition ) ) {
651 $class = HTMLForm::$typeMappings[$definition['type']];
652 }
653 if ( $class !== HTMLInfoField::class ) {
654 $definition['tabindex'] = $i;
655 $i++;
656 }
657 }
658 }
659
665 protected function getToken() {
666 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
667 . $this->getName() );
668 }
669
675 protected function getTokenName() {
676 return 'wpAuthToken';
677 }
678
688 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
689 $formDescriptor = [];
690 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
691 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
692 }
693
694 $requestSnapshot = serialize( $requests );
695 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
696 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
697 $formDescriptor, $action );
698 if ( $requestSnapshot !== serialize( $requests ) ) {
699 LoggerFactory::getInstance( 'authentication' )->warning(
700 'AuthChangeFormFields hook changed auth requests' );
701 }
702
703 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
704 // subscribers (who only see one field at a time) to influence ordering.
705 self::sortFormDescriptorFields( $formDescriptor );
706
707 return $formDescriptor;
708 }
709
717 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
718 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
719 $descriptor = [
720 'type' => $type,
721 // Do not prefix input name with 'wp'. This is important for the redirect flow.
722 'name' => $fieldName,
723 ];
724
725 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
726 $descriptor['default'] = $singleFieldInfo['label']->plain();
727 } elseif ( $type !== 'submit' ) {
728 $descriptor += array_filter( [
729 // help-message is omitted as it is usually not really useful for a web interface
730 'label-message' => self::getField( $singleFieldInfo, 'label' ),
731 ] );
732
733 if ( isset( $singleFieldInfo['options'] ) ) {
734 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
736 return $message->parse();
737 }, $singleFieldInfo['options'] ) );
738 }
739
740 if ( isset( $singleFieldInfo['value'] ) ) {
741 $descriptor['default'] = $singleFieldInfo['value'];
742 }
743
744 if ( empty( $singleFieldInfo['optional'] ) ) {
745 $descriptor['required'] = true;
746 }
747 }
748
749 return $descriptor;
750 }
751
758 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
759 $i = 0;
760 foreach ( $formDescriptor as &$field ) {
761 $field['__index'] = $i++;
762 }
763 uasort( $formDescriptor, static function ( $first, $second ) {
764 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
765 ?: $first['__index'] <=> $second['__index'];
766 } );
767 foreach ( $formDescriptor as &$field ) {
768 unset( $field['__index'] );
769 }
770 }
771
779 protected static function getField( array $array, $fieldName, $default = null ) {
780 if ( array_key_exists( $fieldName, $array ) ) {
781 return $array[$fieldName];
782 } else {
783 return $default;
784 }
785 }
786
793 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
794 $map = [
795 'string' => 'text',
796 'password' => 'password',
797 'select' => 'select',
798 'checkbox' => 'check',
799 'multiselect' => 'multiselect',
800 'button' => 'submit',
801 'hidden' => 'hidden',
802 'null' => 'info',
803 ];
804 if ( !array_key_exists( $type, $map ) ) {
805 throw new \LogicException( 'invalid field type: ' . $type );
806 }
807 return $map[$type];
808 }
809
822 protected static function mergeDefaultFormDescriptor(
823 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
824 ) {
825 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
826 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
827 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
828 // to something in the fieldinfo, and is not an info field or a submit button
829 if (
830 !isset( $fieldInfo[$fieldName] )
831 && (
832 !isset( $defaultField['baseField'] )
833 || !isset( $fieldInfo[$defaultField['baseField']] )
834 )
835 && (
836 !isset( $defaultField['type'] )
837 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
838 )
839 ) {
840 $defaultFormDescriptor[$fieldName] = null;
841 continue;
842 }
843
844 // default message labels should always take priority
845 $requestField = $formDescriptor[$fieldName] ?? [];
846 if (
847 isset( $defaultField['label'] )
848 || isset( $defaultField['label-message'] )
849 || isset( $defaultField['label-raw'] )
850 ) {
851 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
852 }
853
854 $defaultFormDescriptor[$fieldName] += $requestField;
855 }
856
857 return array_filter( $defaultFormDescriptor + $formDescriptor );
858 }
859}
860
865class_alias( AuthManagerSpecialPage::class, 'AuthManagerSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:192
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:158
An information field (text blob), not a proper input.
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.
Variant of the Message class.
Create PSR-3 logger objects.
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 stripping il...
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 name of this Special Page.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:58
Generic operation result class Has warning/error list, boolean status and arbitrary value.