MediaWiki REL1_35
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
9
17abstract class AuthManagerSpecialPage extends SpecialPage {
21 protected static $allowedActions = [
22 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
23 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
24 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
25 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
26 ];
27
29 protected static $messages = [];
30
32 protected $authAction;
33
35 protected $authRequests;
36
38 protected $subPage;
39
41 protected $isReturn;
42
44 protected $savedRequest;
45
58 public function onAuthChangeFormFields(
59 array $requests, array $fieldInfo, array &$formDescriptor, $action
60 ) {
61 }
62
67 protected function getLoginSecurityLevel() {
68 return $this->getName();
69 }
70
71 public function getRequest() {
72 return $this->savedRequest ?: $this->getContext()->getRequest();
73 }
74
85 protected function setRequest( array $data, $wasPosted = null ) {
86 $request = $this->getContext()->getRequest();
87 if ( $wasPosted === null ) {
88 $wasPosted = $request->wasPosted();
89 }
90 $this->savedRequest = new DerivativeRequest( $request, $data + $request->getQueryValues(),
91 $wasPosted );
92 }
93
100 protected function beforeExecute( $subPage ) {
101 $this->getOutput()->disallowUserJs();
102
103 return $this->handleReturnBeforeExecute( $subPage )
105 }
106
123 protected function handleReturnBeforeExecute( $subPage ) {
124 $authManager = MediaWikiServices::getInstance()->getAuthManager();
125 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
126
127 if ( $subPage === 'return' ) {
128 $this->loadAuth( $subPage );
129 $preservedParams = $this->getPreservedParams( false );
130
131 // FIXME save POST values only from request
132 $authData = array_diff_key( $this->getRequest()->getValues(),
133 $preservedParams, [ 'title' => 1 ] );
134 $authManager->setAuthenticationSessionData( $key, $authData );
135
136 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
137 $this->getOutput()->redirect( $url );
138 return false;
139 }
140
141 $authData = $authManager->getAuthenticationSessionData( $key );
142 if ( $authData ) {
143 $authManager->removeAuthenticationSessionData( $key );
144 $this->isReturn = true;
145 $this->setRequest( $authData, true );
146 }
147
148 return true;
149 }
150
161 protected function handleReauthBeforeExecute( $subPage ) {
162 $authManager = MediaWikiServices::getInstance()->getAuthManager();
163 $request = $this->getRequest();
164 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
165
166 $securityLevel = $this->getLoginSecurityLevel();
167 if ( $securityLevel ) {
168 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
169 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
170 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
171
172 if ( $request->wasPosted() ) {
173 // unique ID in case the same special page is open in multiple browser tabs
174 $uniqueId = MWCryptRand::generateHex( 6 );
175 $key .= ':' . $uniqueId;
176
177 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
178 $authData = array_diff_key( $request->getValues(),
179 $this->getPreservedParams( false ), [ 'title' => 1 ] );
180 $authManager->setAuthenticationSessionData( $key, $authData );
181 }
182
183 $title = SpecialPage::getTitleFor( 'Userlogin' );
184 $url = $title->getFullURL( [
185 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
186 'returntoquery' => wfArrayToCgi( $queryParams ),
187 'force' => $securityLevel,
188 ], false, PROTO_HTTPS );
189
190 $this->getOutput()->redirect( $url );
191 return false;
192 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
193 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
194 }
195 }
196
197 $uniqueId = $request->getVal( 'authUniqueId' );
198 if ( $uniqueId ) {
199 $key .= ':' . $uniqueId;
200 $authData = $authManager->getAuthenticationSessionData( $key );
201 if ( $authData ) {
202 $authManager->removeAuthenticationSessionData( $key );
203 $this->setRequest( $authData, true );
204 }
205 }
206
207 return true;
208 }
209
217 abstract protected function getDefaultAction( $subPage );
218
225 protected function messageKey( $defaultKey ) {
226 return array_key_exists( $defaultKey, static::$messages )
227 ? static::$messages[$defaultKey] : $defaultKey;
228 }
229
235 protected function getRequestBlacklist() {
236 return [];
237 }
238
249 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
250 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
251 // calls. This is important for requests which have hidden information so any
252 // getAuthenticationRequests call would mean putting data into some cache.
253 if (
254 !$reset && $this->subPage === $subPage && $this->authAction
255 && ( !$authAction || $authAction === $this->authAction )
256 ) {
257 return;
258 }
259
260 $request = $this->getRequest();
261 $this->subPage = $subPage;
262 $this->authAction = $authAction ?: $request->getText( 'authAction' );
263 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
264 $this->authAction = $this->getDefaultAction( $subPage );
265 if ( $request->wasPosted() ) {
266 $continueAction = $this->getContinueAction( $this->authAction );
267 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
268 $this->authAction = $continueAction;
269 }
270 }
271 }
272
273 $allReqs = MediaWikiServices::getInstance()->getAuthManager()->getAuthenticationRequests(
274 $this->authAction, $this->getUser() );
275 $this->authRequests = array_filter( $allReqs, function ( $req ) {
276 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
277 } );
278 }
279
284 protected function isContinued() {
285 return in_array( $this->authAction, [
286 AuthManager::ACTION_LOGIN_CONTINUE,
287 AuthManager::ACTION_CREATE_CONTINUE,
288 AuthManager::ACTION_LINK_CONTINUE,
289 ], true );
290 }
291
297 protected function getContinueAction( $action ) {
298 switch ( $action ) {
299 case AuthManager::ACTION_LOGIN:
300 $action = AuthManager::ACTION_LOGIN_CONTINUE;
301 break;
302 case AuthManager::ACTION_CREATE:
303 $action = AuthManager::ACTION_CREATE_CONTINUE;
304 break;
305 case AuthManager::ACTION_LINK:
306 $action = AuthManager::ACTION_LINK_CONTINUE;
307 break;
308 }
309 return $action;
310 }
311
320 protected function isActionAllowed( $action ) {
321 $authManager = MediaWikiServices::getInstance()->getAuthManager();
322 if ( !in_array( $action, static::$allowedActions, true ) ) {
323 throw new InvalidArgumentException( 'invalid action: ' . $action );
324 }
325
326 // calling getAuthenticationRequests can be expensive, avoid if possible
327 $requests = ( $action === $this->authAction ) ? $this->authRequests
328 : $authManager->getAuthenticationRequests( $action );
329 if ( !$requests ) {
330 // no provider supports this action in the current state
331 return false;
332 }
333
334 switch ( $action ) {
335 case AuthManager::ACTION_LOGIN:
336 case AuthManager::ACTION_LOGIN_CONTINUE:
337 return $authManager->canAuthenticateNow();
338 case AuthManager::ACTION_CREATE:
339 case AuthManager::ACTION_CREATE_CONTINUE:
340 return $authManager->canCreateAccounts();
341 case AuthManager::ACTION_LINK:
342 case AuthManager::ACTION_LINK_CONTINUE:
343 return $authManager->canLinkAccounts();
344 case AuthManager::ACTION_CHANGE:
345 case AuthManager::ACTION_REMOVE:
346 case AuthManager::ACTION_UNLINK:
347 return true;
348 default:
349 // should never reach here but makes static code analyzers happy
350 throw new InvalidArgumentException( 'invalid action: ' . $action );
351 }
352 }
353
360 protected function performAuthenticationStep( $action, array $requests ) {
361 if ( !in_array( $action, static::$allowedActions, true ) ) {
362 throw new InvalidArgumentException( 'invalid action: ' . $action );
363 }
364
365 $authManager = MediaWikiServices::getInstance()->getAuthManager();
366 $returnToUrl = $this->getPageTitle( 'return' )
367 ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
368
369 switch ( $action ) {
370 case AuthManager::ACTION_LOGIN:
371 return $authManager->beginAuthentication( $requests, $returnToUrl );
372 case AuthManager::ACTION_LOGIN_CONTINUE:
373 return $authManager->continueAuthentication( $requests );
374 case AuthManager::ACTION_CREATE:
375 return $authManager->beginAccountCreation( $this->getUser(), $requests,
376 $returnToUrl );
377 case AuthManager::ACTION_CREATE_CONTINUE:
378 return $authManager->continueAccountCreation( $requests );
379 case AuthManager::ACTION_LINK:
380 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
381 case AuthManager::ACTION_LINK_CONTINUE:
382 return $authManager->continueAccountLink( $requests );
383 case AuthManager::ACTION_CHANGE:
384 case AuthManager::ACTION_REMOVE:
385 case AuthManager::ACTION_UNLINK:
386 if ( count( $requests ) > 1 ) {
387 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
388 } elseif ( !$requests ) {
389 throw new InvalidArgumentException( 'no auth request' );
390 }
391 $req = reset( $requests );
392 $status = $authManager->allowsAuthenticationDataChange( $req );
393 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
394 if ( !$status->isGood() ) {
395 return AuthenticationResponse::newFail( $status->getMessage() );
396 }
397 $authManager->changeAuthenticationData( $req );
398 return AuthenticationResponse::newPass();
399 default:
400 // should never reach here but makes static code analyzers happy
401 throw new InvalidArgumentException( 'invalid action: ' . $action );
402 }
403 }
404
415 protected function trySubmit() {
416 $status = false;
417
418 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
419 $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
420
421 if ( $this->getRequest()->wasPosted() ) {
422 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
423 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
424 $sessionToken = $this->getToken();
425 if ( $sessionToken->wasNew() ) {
426 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
427 } elseif ( !$requestTokenValue ) {
428 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
429 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
430 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
431 }
432
433 $form->prepareForm();
434 $status = $form->trySubmit();
435
436 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
437 // FIXME this probably should be in HTMLForm
438 if ( $status === true ) {
439 // not supposed to happen since our submit handler should always return a Status
440 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
441 } elseif ( $status === false ) {
442 // form was not submitted; nothing to do
443 } elseif ( $status instanceof Status ) {
444 // already handled by the form; nothing to do
445 } elseif ( $status instanceof StatusValue ) {
446 // in theory not an allowed return type but nothing stops the submit handler from
447 // accidentally returning it so best check and fix
448 $status = Status::wrap( $status );
449 } elseif ( is_string( $status ) ) {
450 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
451 } elseif ( is_array( $status ) ) {
452 if ( is_string( reset( $status ) ) ) {
453 $status = Status::newFatal( ...$status );
454 } elseif ( is_array( reset( $status ) ) ) {
455 $ret = Status::newGood();
456 foreach ( $status as $message ) {
457 $ret->fatal( ...$message );
458 }
459 $status = $ret;
460 } else {
461 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
462 . 'first element of array is ' . gettype( reset( $status ) ) );
463 }
464 } else {
465 // not supposed to happen but HTMLForm does not actually verify the return type
466 // from the submit callback; better safe then sorry
467 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
468 . gettype( $status ) );
469 }
470
471 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
472 // This is awkward. There was a form validation error, which means the data was not
473 // passed to AuthManager. Normally we would display the form with an error message,
474 // but for the data we received via the redirect flow that would not be helpful at all.
475 // Let's just submit the data to AuthManager directly instead.
476 LoggerFactory::getInstance( 'authentication' )
477 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
478 'status' => $status->getWikiText( false, false, 'en' ) ] );
479 $status = $this->handleFormSubmit( $form->mFieldData );
480 }
481 }
482
483 $changeActions = [
484 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
485 ];
486 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
487 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
488 }
489
490 return $status;
491 }
492
499 public function handleFormSubmit( $data ) {
500 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
501 $response = $this->performAuthenticationStep( $this->authAction, $requests );
502
503 // we can't handle FAIL or similar as failure here since it might require changing the form
504 return Status::newGood( $response );
505 }
506
515 protected function getPreservedParams( $withToken = false ) {
516 $params = [];
517 if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
518 $params['authAction'] = $this->getContinueAction( $this->authAction );
519 }
520 if ( $withToken ) {
521 $params[$this->getTokenName()] = $this->getToken()->toString();
522 }
523 return $params;
524 }
525
533 protected function getAuthFormDescriptor( $requests, $action ) {
534 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
535 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
536
537 $this->addTabIndex( $formDescriptor );
538
539 return $formDescriptor;
540 }
541
548 protected function getAuthForm( array $requests, $action ) {
549 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
550 $context = $this->getContext();
551 if ( $context->getRequest() !== $this->getRequest() ) {
552 // We have overridden the request, need to make sure the form uses that too.
553 $context = new DerivativeContext( $this->getContext() );
554 $context->setRequest( $this->getRequest() );
555 }
556 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
557 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
558 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
559 $form->addHiddenField( 'authAction', $this->authAction );
560 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
561
562 return $form;
563 }
564
569 protected function displayForm( $status ) {
570 if ( $status instanceof StatusValue ) {
571 $status = Status::wrap( $status );
572 }
573 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
574 $form->prepareForm()->displayForm( $status );
575 }
576
588 protected function needsSubmitButton( array $requests ) {
589 $customSubmitButtonPresent = false;
590
591 // Secondary and preauth providers always need their data; they will not care what button
592 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
593 // that's the point in being optional. Se we need to check whether all primary providers
594 // have their own buttons and whether there is at least one button present.
595 foreach ( $requests as $req ) {
596 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
597 if ( $this->hasOwnSubmitButton( $req ) ) {
598 $customSubmitButtonPresent = true;
599 } else {
600 return true;
601 }
602 }
603 }
604 return !$customSubmitButtonPresent;
605 }
606
612 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
613 foreach ( $req->getFieldInfo() as $field => $info ) {
614 if ( $info['type'] === 'button' ) {
615 return true;
616 }
617 }
618 return false;
619 }
620
626 protected function addTabIndex( &$formDescriptor ) {
627 $i = 1;
628 foreach ( $formDescriptor as $field => &$definition ) {
629 $class = false;
630 if ( array_key_exists( 'class', $definition ) ) {
631 $class = $definition['class'];
632 } elseif ( array_key_exists( 'type', $definition ) ) {
633 $class = HTMLForm::$typeMappings[$definition['type']];
634 }
635 if ( $class !== HTMLInfoField::class ) {
636 $definition['tabindex'] = $i;
637 $i++;
638 }
639 }
640 }
641
647 protected function getToken() {
648 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
649 . $this->getName() );
650 }
651
657 protected function getTokenName() {
658 return 'wpAuthToken';
659 }
660
670 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
671 $formDescriptor = [];
672 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
673 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
674 }
675
676 $requestSnapshot = serialize( $requests );
677 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
678 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
679 $formDescriptor, $action );
680 if ( $requestSnapshot !== serialize( $requests ) ) {
681 LoggerFactory::getInstance( 'authentication' )->warning(
682 'AuthChangeFormFields hook changed auth requests' );
683 }
684
685 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
686 // subscribers (who only see one field at a time) to influence ordering.
687 self::sortFormDescriptorFields( $formDescriptor );
688
689 return $formDescriptor;
690 }
691
699 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
700 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
701 $descriptor = [
702 'type' => $type,
703 // Do not prefix input name with 'wp'. This is important for the redirect flow.
704 'name' => $fieldName,
705 ];
706
707 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
708 $descriptor['default'] = $singleFieldInfo['label']->plain();
709 } elseif ( $type !== 'submit' ) {
710 $descriptor += array_filter( [
711 // help-message is omitted as it is usually not really useful for a web interface
712 'label-message' => self::getField( $singleFieldInfo, 'label' ),
713 ] );
714
715 if ( isset( $singleFieldInfo['options'] ) ) {
716 $descriptor['options'] = array_flip( array_map( function ( $message ) {
718 return $message->parse();
719 }, $singleFieldInfo['options'] ) );
720 }
721
722 if ( isset( $singleFieldInfo['value'] ) ) {
723 $descriptor['default'] = $singleFieldInfo['value'];
724 }
725
726 if ( empty( $singleFieldInfo['optional'] ) ) {
727 $descriptor['required'] = true;
728 }
729 }
730
731 return $descriptor;
732 }
733
740 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
741 $i = 0;
742 foreach ( $formDescriptor as &$field ) {
743 $field['__index'] = $i++;
744 }
745 uasort( $formDescriptor, function ( $first, $second ) {
746 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
747 ?: $first['__index'] <=> $second['__index'];
748 } );
749 foreach ( $formDescriptor as &$field ) {
750 unset( $field['__index'] );
751 }
752 }
753
761 protected static function getField( array $array, $fieldName, $default = null ) {
762 if ( array_key_exists( $fieldName, $array ) ) {
763 return $array[$fieldName];
764 } else {
765 return $default;
766 }
767 }
768
775 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
776 $map = [
777 'string' => 'text',
778 'password' => 'password',
779 'select' => 'select',
780 'checkbox' => 'check',
781 'multiselect' => 'multiselect',
782 'button' => 'submit',
783 'hidden' => 'hidden',
784 'null' => 'info',
785 ];
786 if ( !array_key_exists( $type, $map ) ) {
787 throw new \LogicException( 'invalid field type: ' . $type );
788 }
789 return $map[$type];
790 }
791
804 protected static function mergeDefaultFormDescriptor(
805 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
806 ) {
807 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
808 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
809 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
810 // to something in the fieldinfo, and is not an info field or a submit button
811 if (
812 !isset( $fieldInfo[$fieldName] )
813 && (
814 !isset( $defaultField['baseField'] )
815 || !isset( $fieldInfo[$defaultField['baseField']] )
816 )
817 && (
818 !isset( $defaultField['type'] )
819 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
820 )
821 ) {
822 $defaultFormDescriptor[$fieldName] = null;
823 continue;
824 }
825
826 // default message labels should always take priority
827 $requestField = $formDescriptor[$fieldName] ?? [];
828 if (
829 isset( $defaultField['label'] )
830 || isset( $defaultField['label-message'] )
831 || isset( $defaultField['label-raw'] )
832 ) {
833 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
834 }
835
836 $defaultFormDescriptor[$fieldName] += $requestField;
837 }
838
839 return array_filter( $defaultFormDescriptor + $formDescriptor );
840 }
841}
serialize()
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.
getLoginSecurityLevel()
Stable to override.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
beforeExecute( $subPage)
Stable to override.
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)
Stable to override.
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-existend 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.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
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.
MediaWikiServices is the service locator for the application scope of MediaWiki.
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.
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...
const PROTO_HTTPS
Definition Defines.php:210