MediaWiki REL1_39
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
8
18abstract class AuthManagerSpecialPage extends SpecialPage {
22 protected static $allowedActions = [
23 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
24 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
25 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
26 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
27 ];
28
30 protected static $messages = [];
31
33 protected $authAction;
34
36 protected $authRequests;
37
39 protected $subPage;
40
42 protected $isReturn;
43
45 protected $savedRequest;
46
59 public function onAuthChangeFormFields(
60 array $requests, array $fieldInfo, array &$formDescriptor, $action
61 ) {
62 }
63
68 protected function getLoginSecurityLevel() {
69 return $this->getName();
70 }
71
72 public function getRequest() {
73 return $this->savedRequest ?: $this->getContext()->getRequest();
74 }
75
86 protected function setRequest( array $data, $wasPosted = null ) {
87 $request = $this->getContext()->getRequest();
88 if ( $wasPosted === null ) {
89 $wasPosted = $request->wasPosted();
90 }
91 $this->savedRequest = new DerivativeRequest( $request, $data + $request->getQueryValues(),
92 $wasPosted );
93 }
94
101 protected function beforeExecute( $subPage ) {
102 $this->getOutput()->disallowUserJs();
103
104 return $this->handleReturnBeforeExecute( $subPage )
106 }
107
124 protected function handleReturnBeforeExecute( $subPage ) {
125 $authManager = $this->getAuthManager();
126 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
127
128 if ( $subPage === 'return' ) {
129 $this->loadAuth( $subPage );
130 $preservedParams = $this->getPreservedParams( false );
131
132 // FIXME save POST values only from request
133 $authData = array_diff_key( $this->getRequest()->getValues(),
134 $preservedParams, [ 'title' => 1 ] );
135 $authManager->setAuthenticationSessionData( $key, $authData );
136
137 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
138 $this->getOutput()->redirect( $url );
139 return false;
140 }
141
142 $authData = $authManager->getAuthenticationSessionData( $key );
143 if ( $authData ) {
144 $authManager->removeAuthenticationSessionData( $key );
145 $this->isReturn = true;
146 $this->setRequest( $authData, true );
147 }
148
149 return true;
150 }
151
162 protected function handleReauthBeforeExecute( $subPage ) {
163 $authManager = $this->getAuthManager();
164 $request = $this->getRequest();
165 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
166
167 $securityLevel = $this->getLoginSecurityLevel();
168 if ( $securityLevel ) {
169 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
170 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
171 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
172
173 if ( $request->wasPosted() ) {
174 // unique ID in case the same special page is open in multiple browser tabs
175 $uniqueId = MWCryptRand::generateHex( 6 );
176 $key .= ':' . $uniqueId;
177
178 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
179 $authData = array_diff_key( $request->getValues(),
180 $this->getPreservedParams( false ), [ 'title' => 1 ] );
181 $authManager->setAuthenticationSessionData( $key, $authData );
182 }
183
184 $title = SpecialPage::getTitleFor( 'Userlogin' );
185 $url = $title->getFullURL( [
186 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
187 'returntoquery' => wfArrayToCgi( $queryParams ),
188 'force' => $securityLevel,
189 ], false, PROTO_HTTPS );
190
191 $this->getOutput()->redirect( $url );
192 return false;
193 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
194 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
195 }
196 }
197
198 $uniqueId = $request->getVal( 'authUniqueId' );
199 if ( $uniqueId ) {
200 $key .= ':' . $uniqueId;
201 $authData = $authManager->getAuthenticationSessionData( $key );
202 if ( $authData ) {
203 $authManager->removeAuthenticationSessionData( $key );
204 $this->setRequest( $authData, true );
205 }
206 }
207
208 return true;
209 }
210
218 abstract protected function getDefaultAction( $subPage );
219
226 protected function messageKey( $defaultKey ) {
227 return array_key_exists( $defaultKey, static::$messages )
228 ? static::$messages[$defaultKey] : $defaultKey;
229 }
230
236 protected function getRequestBlacklist() {
237 return [];
238 }
239
250 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
251 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
252 // calls. This is important for requests which have hidden information so any
253 // getAuthenticationRequests call would mean putting data into some cache.
254 if (
255 !$reset && $this->subPage === $subPage && $this->authAction
256 && ( !$authAction || $authAction === $this->authAction )
257 ) {
258 return;
259 }
260
261 $request = $this->getRequest();
262 $this->subPage = $subPage;
263 $this->authAction = $authAction ?: $request->getText( 'authAction' );
264 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
265 $this->authAction = $this->getDefaultAction( $subPage );
266 if ( $request->wasPosted() ) {
267 $continueAction = $this->getContinueAction( $this->authAction );
268 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
269 $this->authAction = $continueAction;
270 }
271 }
272 }
273
274 $allReqs = $this->getAuthManager()->getAuthenticationRequests(
275 $this->authAction, $this->getUser() );
276 $this->authRequests = array_filter( $allReqs, function ( $req ) {
277 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
278 } );
279 }
280
285 protected function isContinued() {
286 return in_array( $this->authAction, [
287 AuthManager::ACTION_LOGIN_CONTINUE,
288 AuthManager::ACTION_CREATE_CONTINUE,
289 AuthManager::ACTION_LINK_CONTINUE,
290 ], true );
291 }
292
298 protected function getContinueAction( $action ) {
299 switch ( $action ) {
300 case AuthManager::ACTION_LOGIN:
301 $action = AuthManager::ACTION_LOGIN_CONTINUE;
302 break;
303 case AuthManager::ACTION_CREATE:
304 $action = AuthManager::ACTION_CREATE_CONTINUE;
305 break;
306 case AuthManager::ACTION_LINK:
307 $action = AuthManager::ACTION_LINK_CONTINUE;
308 break;
309 }
310 return $action;
311 }
312
321 protected function isActionAllowed( $action ) {
322 $authManager = $this->getAuthManager();
323 if ( !in_array( $action, static::$allowedActions, true ) ) {
324 throw new InvalidArgumentException( 'invalid action: ' . $action );
325 }
326
327 // calling getAuthenticationRequests can be expensive, avoid if possible
328 $requests = ( $action === $this->authAction ) ? $this->authRequests
329 : $authManager->getAuthenticationRequests( $action );
330 if ( !$requests ) {
331 // no provider supports this action in the current state
332 return false;
333 }
334
335 switch ( $action ) {
336 case AuthManager::ACTION_LOGIN:
337 case AuthManager::ACTION_LOGIN_CONTINUE:
338 return $authManager->canAuthenticateNow();
339 case AuthManager::ACTION_CREATE:
340 case AuthManager::ACTION_CREATE_CONTINUE:
341 return $authManager->canCreateAccounts();
342 case AuthManager::ACTION_LINK:
343 case AuthManager::ACTION_LINK_CONTINUE:
344 return $authManager->canLinkAccounts();
345 case AuthManager::ACTION_CHANGE:
346 case AuthManager::ACTION_REMOVE:
347 case AuthManager::ACTION_UNLINK:
348 return true;
349 default:
350 // should never reach here but makes static code analyzers happy
351 throw new InvalidArgumentException( 'invalid action: ' . $action );
352 }
353 }
354
361 protected function performAuthenticationStep( $action, array $requests ) {
362 if ( !in_array( $action, static::$allowedActions, true ) ) {
363 throw new InvalidArgumentException( 'invalid action: ' . $action );
364 }
365
366 $authManager = $this->getAuthManager();
367 $returnToUrl = $this->getPageTitle( 'return' )
368 ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
369
370 switch ( $action ) {
371 case AuthManager::ACTION_LOGIN:
372 return $authManager->beginAuthentication( $requests, $returnToUrl );
373 case AuthManager::ACTION_LOGIN_CONTINUE:
374 return $authManager->continueAuthentication( $requests );
375 case AuthManager::ACTION_CREATE:
376 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
377 $returnToUrl );
378 case AuthManager::ACTION_CREATE_CONTINUE:
379 return $authManager->continueAccountCreation( $requests );
380 case AuthManager::ACTION_LINK:
381 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
382 case AuthManager::ACTION_LINK_CONTINUE:
383 return $authManager->continueAccountLink( $requests );
384 case AuthManager::ACTION_CHANGE:
385 case AuthManager::ACTION_REMOVE:
386 case AuthManager::ACTION_UNLINK:
387 if ( count( $requests ) > 1 ) {
388 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
389 } elseif ( !$requests ) {
390 throw new InvalidArgumentException( 'no auth request' );
391 }
392 $req = reset( $requests );
393 $status = $authManager->allowsAuthenticationDataChange( $req );
394 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
395 if ( !$status->isGood() ) {
396 return AuthenticationResponse::newFail( $status->getMessage() );
397 }
398 $authManager->changeAuthenticationData( $req );
399 return AuthenticationResponse::newPass();
400 default:
401 // should never reach here but makes static code analyzers happy
402 throw new InvalidArgumentException( 'invalid action: ' . $action );
403 }
404 }
405
416 protected function trySubmit() {
417 $status = false;
418
419 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
420 $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
421
422 if ( $this->getRequest()->wasPosted() ) {
423 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
424 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
425 $sessionToken = $this->getToken();
426 if ( $sessionToken->wasNew() ) {
427 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
428 } elseif ( !$requestTokenValue ) {
429 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
430 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
431 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
432 }
433
434 $form->prepareForm();
435 $status = $form->trySubmit();
436
437 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
438 // FIXME this probably should be in HTMLForm
439 if ( $status === true ) {
440 // not supposed to happen since our submit handler should always return a Status
441 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
442 } elseif ( $status === false ) {
443 // form was not submitted; nothing to do
444 } elseif ( $status instanceof Status ) {
445 // already handled by the form; nothing to do
446 } elseif ( $status instanceof StatusValue ) {
447 // in theory not an allowed return type but nothing stops the submit handler from
448 // accidentally returning it so best check and fix
449 $status = Status::wrap( $status );
450 } elseif ( is_string( $status ) ) {
451 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
452 } elseif ( is_array( $status ) ) {
453 if ( is_string( reset( $status ) ) ) {
454 // @phan-suppress-next-line PhanParamTooFewUnpack
455 $status = Status::newFatal( ...$status );
456 } elseif ( is_array( reset( $status ) ) ) {
457 $ret = Status::newGood();
458 foreach ( $status as $message ) {
459 // @phan-suppress-next-line PhanParamTooFewUnpack
460 $ret->fatal( ...$message );
461 }
462 $status = $ret;
463 } else {
464 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
465 . 'first element of array is ' . gettype( reset( $status ) ) );
466 }
467 } else {
468 // not supposed to happen but HTMLForm does not actually verify the return type
469 // from the submit callback; better safe then sorry
470 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
471 . gettype( $status ) );
472 }
473
474 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
475 // This is awkward. There was a form validation error, which means the data was not
476 // passed to AuthManager. Normally we would display the form with an error message,
477 // but for the data we received via the redirect flow that would not be helpful at all.
478 // Let's just submit the data to AuthManager directly instead.
479 LoggerFactory::getInstance( 'authentication' )
480 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
481 'status' => $status->getWikiText( false, false, 'en' ) ] );
482 $status = $this->handleFormSubmit( $form->mFieldData );
483 }
484 }
485
486 $changeActions = [
487 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
488 ];
489 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
490 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
491 }
492
493 return $status;
494 }
495
502 public function handleFormSubmit( $data ) {
503 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
504 $response = $this->performAuthenticationStep( $this->authAction, $requests );
505
506 // we can't handle FAIL or similar as failure here since it might require changing the form
507 return Status::newGood( $response );
508 }
509
518 protected function getPreservedParams( $withToken = false ) {
519 $params = [];
520 if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
521 $params['authAction'] = $this->getContinueAction( $this->authAction );
522 }
523 if ( $withToken ) {
524 $params[$this->getTokenName()] = $this->getToken()->toString();
525 }
526 return $params;
527 }
528
536 protected function getAuthFormDescriptor( $requests, $action ) {
537 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
538 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
539
540 $this->addTabIndex( $formDescriptor );
541
542 return $formDescriptor;
543 }
544
551 protected function getAuthForm( array $requests, $action ) {
552 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
553 $context = $this->getContext();
554 if ( $context->getRequest() !== $this->getRequest() ) {
555 // We have overridden the request, need to make sure the form uses that too.
556 $context = new DerivativeContext( $this->getContext() );
557 $context->setRequest( $this->getRequest() );
558 }
559 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
560 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
561 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
562 $form->addHiddenField( 'authAction', $this->authAction );
563 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
564
565 return $form;
566 }
567
572 protected function displayForm( $status ) {
573 if ( $status instanceof StatusValue ) {
574 $status = Status::wrap( $status );
575 }
576 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
577 $form->prepareForm()->displayForm( $status );
578 }
579
591 protected function needsSubmitButton( array $requests ) {
592 $customSubmitButtonPresent = false;
593
594 // Secondary and preauth providers always need their data; they will not care what button
595 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
596 // that's the point in being optional. Se we need to check whether all primary providers
597 // have their own buttons and whether there is at least one button present.
598 foreach ( $requests as $req ) {
599 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
600 if ( $this->hasOwnSubmitButton( $req ) ) {
601 $customSubmitButtonPresent = true;
602 } else {
603 return true;
604 }
605 }
606 }
607 return !$customSubmitButtonPresent;
608 }
609
615 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
616 foreach ( $req->getFieldInfo() as $field => $info ) {
617 if ( $info['type'] === 'button' ) {
618 return true;
619 }
620 }
621 return false;
622 }
623
629 protected function addTabIndex( &$formDescriptor ) {
630 $i = 1;
631 foreach ( $formDescriptor as $field => &$definition ) {
632 $class = false;
633 if ( array_key_exists( 'class', $definition ) ) {
634 $class = $definition['class'];
635 } elseif ( array_key_exists( 'type', $definition ) ) {
636 $class = HTMLForm::$typeMappings[$definition['type']];
637 }
638 if ( $class !== HTMLInfoField::class ) {
639 $definition['tabindex'] = $i;
640 $i++;
641 }
642 }
643 }
644
650 protected function getToken() {
651 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
652 . $this->getName() );
653 }
654
660 protected function getTokenName() {
661 return 'wpAuthToken';
662 }
663
673 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
674 $formDescriptor = [];
675 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
676 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
677 }
678
679 $requestSnapshot = serialize( $requests );
680 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
681 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
682 $formDescriptor, $action );
683 if ( $requestSnapshot !== serialize( $requests ) ) {
684 LoggerFactory::getInstance( 'authentication' )->warning(
685 'AuthChangeFormFields hook changed auth requests' );
686 }
687
688 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
689 // subscribers (who only see one field at a time) to influence ordering.
690 self::sortFormDescriptorFields( $formDescriptor );
691
692 return $formDescriptor;
693 }
694
702 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
703 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
704 $descriptor = [
705 'type' => $type,
706 // Do not prefix input name with 'wp'. This is important for the redirect flow.
707 'name' => $fieldName,
708 ];
709
710 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
711 $descriptor['default'] = $singleFieldInfo['label']->plain();
712 } elseif ( $type !== 'submit' ) {
713 $descriptor += array_filter( [
714 // help-message is omitted as it is usually not really useful for a web interface
715 'label-message' => self::getField( $singleFieldInfo, 'label' ),
716 ] );
717
718 if ( isset( $singleFieldInfo['options'] ) ) {
719 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
721 return $message->parse();
722 }, $singleFieldInfo['options'] ) );
723 }
724
725 if ( isset( $singleFieldInfo['value'] ) ) {
726 $descriptor['default'] = $singleFieldInfo['value'];
727 }
728
729 if ( empty( $singleFieldInfo['optional'] ) ) {
730 $descriptor['required'] = true;
731 }
732 }
733
734 return $descriptor;
735 }
736
743 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
744 $i = 0;
745 foreach ( $formDescriptor as &$field ) {
746 $field['__index'] = $i++;
747 }
748 uasort( $formDescriptor, function ( $first, $second ) {
749 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
750 ?: $first['__index'] <=> $second['__index'];
751 } );
752 foreach ( $formDescriptor as &$field ) {
753 unset( $field['__index'] );
754 }
755 }
756
764 protected static function getField( array $array, $fieldName, $default = null ) {
765 if ( array_key_exists( $fieldName, $array ) ) {
766 return $array[$fieldName];
767 } else {
768 return $default;
769 }
770 }
771
778 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
779 $map = [
780 'string' => 'text',
781 'password' => 'password',
782 'select' => 'select',
783 'checkbox' => 'check',
784 'multiselect' => 'multiselect',
785 'button' => 'submit',
786 'hidden' => 'hidden',
787 'null' => 'info',
788 ];
789 if ( !array_key_exists( $type, $map ) ) {
790 throw new \LogicException( 'invalid field type: ' . $type );
791 }
792 return $map[$type];
793 }
794
807 protected static function mergeDefaultFormDescriptor(
808 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
809 ) {
810 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
811 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
812 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
813 // to something in the fieldinfo, and is not an info field or a submit button
814 if (
815 !isset( $fieldInfo[$fieldName] )
816 && (
817 !isset( $defaultField['baseField'] )
818 || !isset( $fieldInfo[$defaultField['baseField']] )
819 )
820 && (
821 !isset( $defaultField['type'] )
822 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
823 )
824 ) {
825 $defaultFormDescriptor[$fieldName] = null;
826 continue;
827 }
828
829 // default message labels should always take priority
830 $requestField = $formDescriptor[$fieldName] ?? [];
831 if (
832 isset( $defaultField['label'] )
833 || isset( $defaultField['label-message'] )
834 || isset( $defaultField['label-raw'] )
835 ) {
836 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
837 }
838
839 $defaultFormDescriptor[$fieldName] += $requestField;
840 }
841
842 return array_filter( $defaultFormDescriptor + $formDescriptor );
843 }
844}
serialize()
const PROTO_HTTPS
Definition Defines.php:194
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
handleReturnBeforeExecute( $subPage)
Handle redirection from the /return subpage.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
WebRequest null $savedRequest
If set, will be used instead of the real request.
string $authAction
one of the AuthManager::ACTION_* constants.
performAuthenticationStep( $action, array $requests)
getAuthForm(array $requests, $action)
displayForm( $status)
Display the form.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
bool $isReturn
True if the current request is a result of returning from a redirect flow.
static mapFieldInfoTypeToFormDescriptorType( $type)
Maps AuthenticationRequest::getFieldInfo() types to HTMLForm types.
fieldInfoToFormDescriptor(array $requests, array $fieldInfo, $action)
Turns a field info array into a form descriptor.
getDefaultAction( $subPage)
Get the default action for this special page, if none is given via URL/POST data.
handleFormSubmit( $data)
Submit handler callback for HTMLForm.
static array $messages
Customized messages.
AuthenticationRequest[] $authRequests
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
string $subPage
Subpage of the special page.
isContinued()
Returns true if this is not the first step of the authentication.
static mergeDefaultFormDescriptor(array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor)
Apply defaults to a form descriptor, without creating non-existent fields.
hasOwnSubmitButton(AuthenticationRequest $req)
Checks whether the given AuthenticationRequest has its own submit button.
getRequestBlacklist()
Allows blacklisting certain request types.
static mapSingleFieldInfo( $singleFieldInfo, $fieldName)
Maps an authentication field configuration for a single field (as returned by AuthenticationRequest::...
static getField(array $array, $fieldName, $default=null)
Get an array value, or a default if it does not exist.
getRequest()
Get the WebRequest being used for this instance.
messageKey( $defaultKey)
Return custom message key.
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
trySubmit()
Attempts to do an authentication step with the submitted data.
getToken()
Returns the CSRF token.
static sortFormDescriptorFields(array &$formDescriptor)
Sort the fields of a form descriptor by their 'weight' property.
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
getAuthFormDescriptor( $requests, $action)
Generates a HTMLForm descriptor array from a set of authentication requests.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form.
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
static string[] $allowedActions
The list of actions this special page deals with.
An IContextSource implementation which will inherit context from another source but allow individual ...
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
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.
PSR-3 logger instance factory.
Value object representing a CSRF token.
Definition Token.php:32
Variant of the Message class.
Parent class for all special pages.
getName()
Get the name of this Special Page.
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
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,...
getContext()
Gets the context this SpecialPage is executed in.
getAuthority()
Shortcut to get the Authority executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...