MediaWiki master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
4
6use InvalidArgumentException;
7use LogicException;
21use MWCryptRand;
22use StatusValue;
23use UnexpectedValueException;
24
35abstract class AuthManagerSpecialPage extends SpecialPage {
39 protected static $allowedActions = [
40 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
41 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
42 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
43 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
44 ];
45
47 protected static $messages = [];
48
50 protected $authAction;
51
53 protected $authRequests;
54
56 protected $subPage;
57
59 protected $isReturn;
60
62 protected $savedRequest;
63
76 public function onAuthChangeFormFields(
77 array $requests, array $fieldInfo, array &$formDescriptor, $action
78 ) {
79 }
80
85 protected function getLoginSecurityLevel() {
86 return $this->getName();
87 }
88
89 public function getRequest() {
90 return $this->savedRequest ?: $this->getContext()->getRequest();
91 }
92
103 protected function setRequest( array $data, $wasPosted = null ) {
104 $request = $this->getContext()->getRequest();
105 $this->savedRequest = new DerivativeRequest(
106 $request,
107 $data + $request->getQueryValues(),
108 $wasPosted ?? $request->wasPosted()
109 );
110 }
111
118 protected function beforeExecute( $subPage ) {
119 $this->getOutput()->disallowUserJs();
120
121 return $this->handleReturnBeforeExecute( $subPage )
123 }
124
141 protected function handleReturnBeforeExecute( $subPage ) {
142 $authManager = $this->getAuthManager();
143 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
144
145 if ( $subPage === 'return' ) {
146 $this->loadAuth( $subPage );
147 $preservedParams = $this->getPreservedParams();
148
149 // FIXME save POST values only from request
150 $authData = array_diff_key( $this->getRequest()->getValues(),
151 $preservedParams, [ 'title' => 1 ] );
152 $authManager->setAuthenticationSessionData( $key, $authData );
153
154 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
155 $this->getOutput()->redirect( $url );
156 return false;
157 }
158
159 $authData = $authManager->getAuthenticationSessionData( $key );
160 if ( $authData ) {
161 $authManager->removeAuthenticationSessionData( $key );
162 $this->isReturn = true;
163 $this->setRequest( $authData, true );
164 }
165
166 return true;
167 }
168
179 protected function handleReauthBeforeExecute( $subPage ) {
180 $authManager = $this->getAuthManager();
181 $request = $this->getRequest();
182 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
183
184 $securityLevel = $this->getLoginSecurityLevel();
185 if ( $securityLevel ) {
186 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
187 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
188 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
189
190 if ( $request->wasPosted() ) {
191 // unique ID in case the same special page is open in multiple browser tabs
192 $uniqueId = MWCryptRand::generateHex( 6 );
193 $key .= ':' . $uniqueId;
194
195 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
196 $authData = array_diff_key( $request->getValues(),
197 $this->getPreservedParams(), [ 'title' => 1 ] );
198 $authManager->setAuthenticationSessionData( $key, $authData );
199 }
200
201 $title = SpecialPage::getTitleFor( 'Userlogin' );
202 $url = $title->getFullURL( [
203 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
204 'returntoquery' => wfArrayToCgi( $queryParams ),
205 'force' => $securityLevel,
206 ], false, PROTO_HTTPS );
207
208 $this->getOutput()->redirect( $url );
209 return false;
210 }
211
212 if ( $securityStatus !== AuthManager::SEC_OK ) {
213 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
214 }
215 }
216
217 $uniqueId = $request->getVal( 'authUniqueId' );
218 if ( $uniqueId ) {
219 $key .= ':' . $uniqueId;
220 $authData = $authManager->getAuthenticationSessionData( $key );
221 if ( $authData ) {
222 $authManager->removeAuthenticationSessionData( $key );
223 $this->setRequest( $authData, true );
224 }
225 }
226
227 return true;
228 }
229
237 abstract protected function getDefaultAction( $subPage );
238
245 protected function messageKey( $defaultKey ) {
246 return array_key_exists( $defaultKey, static::$messages )
247 ? static::$messages[$defaultKey] : $defaultKey;
248 }
249
255 protected function getRequestBlacklist() {
256 return [];
257 }
258
269 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
270 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
271 // calls. This is important for requests which have hidden information, so any
272 // getAuthenticationRequests call would mean putting data into some cache.
273 if (
274 !$reset && $this->subPage === $subPage && $this->authAction
275 && ( !$authAction || $authAction === $this->authAction )
276 ) {
277 return;
278 }
279
280 $request = $this->getRequest();
281 $this->subPage = $subPage;
282 $this->authAction = $authAction ?: $request->getText( 'authAction' );
283 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
284 $this->authAction = $this->getDefaultAction( $subPage );
285 if ( $request->wasPosted() ) {
286 $continueAction = $this->getContinueAction( $this->authAction );
287 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
288 $this->authAction = $continueAction;
289 }
290 }
291 }
292
293 $allReqs = $this->getAuthManager()->getAuthenticationRequests(
294 $this->authAction, $this->getUser() );
295 $this->authRequests = array_filter( $allReqs, function ( $req ) {
296 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
297 } );
298 }
299
304 protected function isContinued() {
305 return in_array( $this->authAction, [
306 AuthManager::ACTION_LOGIN_CONTINUE,
307 AuthManager::ACTION_CREATE_CONTINUE,
308 AuthManager::ACTION_LINK_CONTINUE,
309 ], true );
310 }
311
317 protected function getContinueAction( $action ) {
318 switch ( $action ) {
319 case AuthManager::ACTION_LOGIN:
320 $action = AuthManager::ACTION_LOGIN_CONTINUE;
321 break;
322 case AuthManager::ACTION_CREATE:
323 $action = AuthManager::ACTION_CREATE_CONTINUE;
324 break;
325 case AuthManager::ACTION_LINK:
326 $action = AuthManager::ACTION_LINK_CONTINUE;
327 break;
328 }
329 return $action;
330 }
331
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( [ 'withToken' => 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 ' . get_debug_type( 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 . get_debug_type( $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
550 protected function getPreservedParams( $options = [] ) {
551 if ( is_bool( $options ) ) {
552 wfDeprecated( __METHOD__ . ' boolean $options', '1.43' );
553 $options = [ 'withToken' => $options ];
554 }
555 $options += [
556 'reset' => false,
557 'withToken' => false,
558 ];
559 // Help Phan figure out that these fields are now definitely set - https://github.com/phan/phan/issues/4864
560 '@phan-var array{reset: bool, withToken: bool} $options';
561 $params = [];
562 $request = $this->getRequest();
563
564 $params += [
565 'uselang' => $request->getVal( 'uselang' ),
566 'variant' => $request->getVal( 'variant' ),
567 'returnto' => $request->getVal( 'returnto' ),
568 'returntoquery' => $request->getVal( 'returntoquery' ),
569 'returntoanchor' => $request->getVal( 'returntoanchor' ),
570 ];
571
572 if ( !$options['reset'] && $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
573 $params['authAction'] = $this->getContinueAction( $this->authAction );
574 }
575
576 if ( $options['withToken'] ) {
577 $params[$this->getTokenName()] = $this->getToken()->toString();
578 }
579
580 // Allow authentication extensions like CentralAuth to preserve their own
581 // query params during and after the authentication process.
582 $this->getHookRunner()->onAuthPreserveQueryParams(
583 $params, [ 'reset' => $options['reset'] ]
584 );
585
586 return array_filter( $params, fn ( $val ) => $val !== null );
587 }
588
596 protected function getAuthFormDescriptor( $requests, $action ) {
597 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
598 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
599
600 $this->addTabIndex( $formDescriptor );
601
602 return $formDescriptor;
603 }
604
611 protected function getAuthForm( array $requests, $action ) {
612 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
613 $context = $this->getContext();
614 if ( $context->getRequest() !== $this->getRequest() ) {
615 // We have overridden the request, need to make sure the form uses that too.
616 $context = new DerivativeContext( $this->getContext() );
617 $context->setRequest( $this->getRequest() );
618 }
619 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
620 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
621 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
622 $form->addHiddenField( 'authAction', $this->authAction );
623 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
624
625 return $form;
626 }
627
632 protected function displayForm( $status ) {
633 if ( $status instanceof StatusValue ) {
634 $status = Status::wrap( $status );
635 }
636 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
637 $form->prepareForm()->displayForm( $status );
638 }
639
651 protected function needsSubmitButton( array $requests ) {
652 $customSubmitButtonPresent = false;
653
654 // Secondary and preauth providers always need their data; they will not care what button
655 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
656 // that's the point in being optional. Se we need to check whether all primary providers
657 // have their own buttons and whether there is at least one button present.
658 foreach ( $requests as $req ) {
659 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
660 if ( $this->hasOwnSubmitButton( $req ) ) {
661 $customSubmitButtonPresent = true;
662 } else {
663 return true;
664 }
665 }
666 }
667 return !$customSubmitButtonPresent;
668 }
669
675 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
676 foreach ( $req->getFieldInfo() as $info ) {
677 if ( $info['type'] === 'button' ) {
678 return true;
679 }
680 }
681 return false;
682 }
683
689 protected function addTabIndex( &$formDescriptor ) {
690 $i = 1;
691 foreach ( $formDescriptor as &$definition ) {
692 $class = false;
693 if ( array_key_exists( 'class', $definition ) ) {
694 $class = $definition['class'];
695 } elseif ( array_key_exists( 'type', $definition ) ) {
696 $class = HTMLForm::$typeMappings[$definition['type']];
697 }
698 if ( $class !== HTMLInfoField::class ) {
699 $definition['tabindex'] = $i;
700 $i++;
701 }
702 }
703 }
704
710 protected function getToken() {
711 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
712 . $this->getName() );
713 }
714
720 protected function getTokenName() {
721 return 'wpAuthToken';
722 }
723
733 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
734 $formDescriptor = [];
735 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
736 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
737 }
738
739 $requestSnapshot = serialize( $requests );
740 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
741 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
742 $formDescriptor, $action );
743 if ( $requestSnapshot !== serialize( $requests ) ) {
744 LoggerFactory::getInstance( 'authentication' )->warning(
745 'AuthChangeFormFields hook changed auth requests' );
746 }
747
748 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
749 // subscribers (who only see one field at a time) to influence ordering.
750 self::sortFormDescriptorFields( $formDescriptor );
751
752 return $formDescriptor;
753 }
754
762 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
763 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
764 $descriptor = [
765 'type' => $type,
766 // Do not prefix input name with 'wp'. This is important for the redirect flow.
767 'name' => $fieldName,
768 ];
769
770 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
771 $descriptor['default'] = $singleFieldInfo['label']->plain();
772 } elseif ( $type !== 'submit' ) {
773 $descriptor += array_filter( [
774 // help-message is omitted as it is usually not really useful for a web interface
775 'label-message' => self::getField( $singleFieldInfo, 'label' ),
776 ] );
777
778 if ( isset( $singleFieldInfo['options'] ) ) {
779 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
781 return $message->parse();
782 }, $singleFieldInfo['options'] ) );
783 }
784
785 if ( isset( $singleFieldInfo['value'] ) ) {
786 $descriptor['default'] = $singleFieldInfo['value'];
787 }
788
789 if ( empty( $singleFieldInfo['optional'] ) ) {
790 $descriptor['required'] = true;
791 }
792 }
793
794 return $descriptor;
795 }
796
803 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
804 $i = 0;
805 foreach ( $formDescriptor as &$field ) {
806 $field['__index'] = $i++;
807 }
808 unset( $field );
809 uasort( $formDescriptor, static function ( $first, $second ) {
810 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
811 ?: $first['__index'] <=> $second['__index'];
812 } );
813 foreach ( $formDescriptor as &$field ) {
814 unset( $field['__index'] );
815 }
816 }
817
825 protected static function getField( array $array, $fieldName, $default = null ) {
826 if ( array_key_exists( $fieldName, $array ) ) {
827 return $array[$fieldName];
828 } else {
829 return $default;
830 }
831 }
832
840 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
841 $map = [
842 'string' => 'text',
843 'password' => 'password',
844 'select' => 'select',
845 'checkbox' => 'check',
846 'multiselect' => 'multiselect',
847 'button' => 'submit',
848 'hidden' => 'hidden',
849 'null' => 'info',
850 ];
851 if ( !array_key_exists( $type, $map ) ) {
852 throw new InvalidArgumentException( 'invalid field type: ' . $type );
853 }
854 return $map[$type];
855 }
856
869 protected static function mergeDefaultFormDescriptor(
870 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
871 ) {
872 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
873 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
874 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
875 // to something in the fieldinfo, and is not an info field or a submit button
876 if (
877 !isset( $fieldInfo[$fieldName] )
878 && (
879 !isset( $defaultField['baseField'] )
880 || !isset( $fieldInfo[$defaultField['baseField']] )
881 )
882 && (
883 !isset( $defaultField['type'] )
884 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
885 )
886 ) {
887 $defaultFormDescriptor[$fieldName] = null;
888 continue;
889 }
890
891 // default message labels should always take priority
892 $requestField = $formDescriptor[$fieldName] ?? [];
893 if (
894 isset( $defaultField['label'] )
895 || isset( $defaultField['label-message'] )
896 || isset( $defaultField['label-raw'] )
897 ) {
898 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
899 }
900
901 $defaultFormDescriptor[$fieldName] += $requestField;
902 }
903
904 return array_filter( $defaultFormDescriptor + $formDescriptor );
905 }
906}
907
909class_alias( AuthManagerSpecialPage::class, 'AuthManagerSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:205
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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 ...
An information field (text blob), not a proper input.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:209
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:155
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:34
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::...
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.
getPreservedParams( $options=[])
Returns URL query parameters which should be preserved between authentication requests.
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.