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
340 protected function isActionAllowed( $action ) {
341 $authManager = $this->getAuthManager();
342 if ( !in_array( $action, static::$allowedActions, true ) ) {
343 throw new InvalidArgumentException( 'invalid action: ' . $action );
344 }
345
346 // calling getAuthenticationRequests can be expensive, avoid if possible
347 $requests = ( $action === $this->authAction ) ? $this->authRequests
348 : $authManager->getAuthenticationRequests( $action );
349 if ( !$requests ) {
350 // no provider supports this action in the current state
351 return false;
352 }
353
354 switch ( $action ) {
355 case AuthManager::ACTION_LOGIN:
356 case AuthManager::ACTION_LOGIN_CONTINUE:
357 return $authManager->canAuthenticateNow();
358 case AuthManager::ACTION_CREATE:
359 case AuthManager::ACTION_CREATE_CONTINUE:
360 return $authManager->canCreateAccounts();
361 case AuthManager::ACTION_LINK:
362 case AuthManager::ACTION_LINK_CONTINUE:
363 return $authManager->canLinkAccounts();
364 case AuthManager::ACTION_CHANGE:
365 case AuthManager::ACTION_REMOVE:
366 case AuthManager::ACTION_UNLINK:
367 return true;
368 default:
369 // should never reach here but makes static code analyzers happy
370 throw new InvalidArgumentException( 'invalid action: ' . $action );
371 }
372 }
373
380 protected function performAuthenticationStep( $action, array $requests ) {
381 if ( !in_array( $action, static::$allowedActions, true ) ) {
382 throw new InvalidArgumentException( 'invalid action: ' . $action );
383 }
384
385 $authManager = $this->getAuthManager();
386 $returnToUrl = $this->getPageTitle( 'return' )
387 ->getFullURL( $this->getPreservedParams( [ 'withToken' => true ] ), false, PROTO_HTTPS );
388
389 switch ( $action ) {
390 case AuthManager::ACTION_LOGIN:
391 return $authManager->beginAuthentication( $requests, $returnToUrl );
392 case AuthManager::ACTION_LOGIN_CONTINUE:
393 return $authManager->continueAuthentication( $requests );
394 case AuthManager::ACTION_CREATE:
395 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
396 $returnToUrl );
397 case AuthManager::ACTION_CREATE_CONTINUE:
398 return $authManager->continueAccountCreation( $requests );
399 case AuthManager::ACTION_LINK:
400 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
401 case AuthManager::ACTION_LINK_CONTINUE:
402 return $authManager->continueAccountLink( $requests );
403 case AuthManager::ACTION_CHANGE:
404 case AuthManager::ACTION_REMOVE:
405 case AuthManager::ACTION_UNLINK:
406 if ( count( $requests ) > 1 ) {
407 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
408 }
409
410 if ( !$requests ) {
411 throw new InvalidArgumentException( 'no auth request' );
412 }
413 $req = reset( $requests );
414 $status = $authManager->allowsAuthenticationDataChange( $req );
415 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
416 if ( !$status->isGood() ) {
417 return AuthenticationResponse::newFail( $status->getMessage() );
418 }
419 $authManager->changeAuthenticationData( $req );
420 return AuthenticationResponse::newPass();
421 default:
422 // should never reach here but makes static code analyzers happy
423 throw new InvalidArgumentException( 'invalid action: ' . $action );
424 }
425 }
426
437 protected function trySubmit() {
438 $status = false;
439
440 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
441 $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
442
443 if ( $this->getRequest()->wasPosted() ) {
444 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
445 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
446 $sessionToken = $this->getToken();
447 if ( $sessionToken->wasNew() ) {
448 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
449 } elseif ( !$requestTokenValue ) {
450 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
451 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
452 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
453 }
454
455 $form->prepareForm();
456 $status = $form->trySubmit();
457
458 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
459 // FIXME this probably should be in HTMLForm
460 if ( $status === true ) {
461 // not supposed to happen since our submit handler should always return a Status
462 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
463 } elseif ( $status === false ) {
464 // form was not submitted; nothing to do
465 } elseif ( $status instanceof Status ) {
466 // already handled by the form; nothing to do
467 } elseif ( $status instanceof StatusValue ) {
468 // in theory not an allowed return type but nothing stops the submit handler from
469 // accidentally returning it so best check and fix
470 $status = Status::wrap( $status );
471 } elseif ( is_string( $status ) ) {
472 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
473 } elseif ( is_array( $status ) ) {
474 if ( is_string( reset( $status ) ) ) {
475 // @phan-suppress-next-line PhanParamTooFewUnpack
476 $status = Status::newFatal( ...$status );
477 } elseif ( is_array( reset( $status ) ) ) {
478 $ret = Status::newGood();
479 foreach ( $status as $message ) {
480 // @phan-suppress-next-line PhanParamTooFewUnpack
481 $ret->fatal( ...$message );
482 }
483 $status = $ret;
484 } else {
485 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
486 . 'first element of array is ' . gettype( reset( $status ) ) );
487 }
488 } else {
489 // not supposed to happen, but HTMLForm does not verify the return type
490 // from the submit callback; better safe then sorry!
491 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
492 . gettype( $status ) );
493 }
494
495 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
496 // This is awkward. There was a form validation error, which means the data was not
497 // passed to AuthManager. Normally we would display the form with an error message,
498 // but for the data we received via the redirect flow that would not be helpful at all.
499 // Let's just submit the data to AuthManager directly instead.
500 LoggerFactory::getInstance( 'authentication' )
501 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
502 'status' => $status->getWikiText( false, false, 'en' ) ] );
503 $status = $this->handleFormSubmit( $form->mFieldData );
504 }
505 }
506
507 $changeActions = [
508 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
509 ];
510 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
511 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
512 }
513
514 return $status;
515 }
516
523 public function handleFormSubmit( $data ) {
524 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
525 $response = $this->performAuthenticationStep( $this->authAction, $requests );
526
527 // we can't handle FAIL or similar as failure here since it might require changing the form
528 return Status::newGood( $response );
529 }
530
551 protected function getPreservedParams( $options = [] ) {
552 if ( is_bool( $options ) ) {
553 wfDeprecated( __METHOD__ . ' boolean $options', '1.43' );
554 $options = [ 'withToken' => $options ];
555 }
556 $options += [
557 'reset' => false,
558 'withToken' => false,
559 ];
560 // Help Phan figure out that these fields are now definitely set - https://github.com/phan/phan/issues/4864
561 '@phan-var array{reset: bool, withToken: bool} $options';
562 $params = [];
563 $request = $this->getRequest();
564
565 $params += [
566 'uselang' => $request->getVal( 'uselang' ),
567 'variant' => $request->getVal( 'variant' ),
568 'returnto' => $request->getVal( 'returnto' ),
569 'returntoquery' => $request->getVal( 'returntoquery' ),
570 'returntoanchor' => $request->getVal( 'returntoanchor' ),
571 ];
572
573 if ( !$options['reset'] && $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
574 $params['authAction'] = $this->getContinueAction( $this->authAction );
575 }
576
577 if ( $options['withToken'] ) {
578 $params[$this->getTokenName()] = $this->getToken()->toString();
579 }
580
581 // Allow authentication extensions like CentralAuth to preserve their own
582 // query params during and after the authentication process.
583 $this->getHookRunner()->onAuthPreserveQueryParams(
584 $params, [ 'reset' => $options['reset'] ]
585 );
586
587 return array_filter( $params, fn ( $val ) => $val !== null );
588 }
589
597 protected function getAuthFormDescriptor( $requests, $action ) {
598 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
599 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
600
601 $this->addTabIndex( $formDescriptor );
602
603 return $formDescriptor;
604 }
605
612 protected function getAuthForm( array $requests, $action ) {
613 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
614 $context = $this->getContext();
615 if ( $context->getRequest() !== $this->getRequest() ) {
616 // We have overridden the request, need to make sure the form uses that too.
617 $context = new DerivativeContext( $this->getContext() );
618 $context->setRequest( $this->getRequest() );
619 }
620 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
621 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
622 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
623 $form->addHiddenField( 'authAction', $this->authAction );
624 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
625
626 return $form;
627 }
628
633 protected function displayForm( $status ) {
634 if ( $status instanceof StatusValue ) {
635 $status = Status::wrap( $status );
636 }
637 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
638 $form->prepareForm()->displayForm( $status );
639 }
640
652 protected function needsSubmitButton( array $requests ) {
653 $customSubmitButtonPresent = false;
654
655 // Secondary and preauth providers always need their data; they will not care what button
656 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
657 // that's the point in being optional. Se we need to check whether all primary providers
658 // have their own buttons and whether there is at least one button present.
659 foreach ( $requests as $req ) {
660 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
661 if ( $this->hasOwnSubmitButton( $req ) ) {
662 $customSubmitButtonPresent = true;
663 } else {
664 return true;
665 }
666 }
667 }
668 return !$customSubmitButtonPresent;
669 }
670
676 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
677 foreach ( $req->getFieldInfo() as $info ) {
678 if ( $info['type'] === 'button' ) {
679 return true;
680 }
681 }
682 return false;
683 }
684
690 protected function addTabIndex( &$formDescriptor ) {
691 $i = 1;
692 foreach ( $formDescriptor as &$definition ) {
693 $class = false;
694 if ( array_key_exists( 'class', $definition ) ) {
695 $class = $definition['class'];
696 } elseif ( array_key_exists( 'type', $definition ) ) {
697 $class = HTMLForm::$typeMappings[$definition['type']];
698 }
699 if ( $class !== HTMLInfoField::class ) {
700 $definition['tabindex'] = $i;
701 $i++;
702 }
703 }
704 }
705
711 protected function getToken() {
712 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
713 . $this->getName() );
714 }
715
721 protected function getTokenName() {
722 return 'wpAuthToken';
723 }
724
734 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
735 $formDescriptor = [];
736 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
737 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
738 }
739
740 $requestSnapshot = serialize( $requests );
741 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
742 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
743 $formDescriptor, $action );
744 if ( $requestSnapshot !== serialize( $requests ) ) {
745 LoggerFactory::getInstance( 'authentication' )->warning(
746 'AuthChangeFormFields hook changed auth requests' );
747 }
748
749 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
750 // subscribers (who only see one field at a time) to influence ordering.
751 self::sortFormDescriptorFields( $formDescriptor );
752
753 return $formDescriptor;
754 }
755
763 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
764 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
765 $descriptor = [
766 'type' => $type,
767 // Do not prefix input name with 'wp'. This is important for the redirect flow.
768 'name' => $fieldName,
769 ];
770
771 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
772 $descriptor['default'] = $singleFieldInfo['label']->plain();
773 } elseif ( $type !== 'submit' ) {
774 $descriptor += array_filter( [
775 // help-message is omitted as it is usually not really useful for a web interface
776 'label-message' => self::getField( $singleFieldInfo, 'label' ),
777 ] );
778
779 if ( isset( $singleFieldInfo['options'] ) ) {
780 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
782 return $message->parse();
783 }, $singleFieldInfo['options'] ) );
784 }
785
786 if ( isset( $singleFieldInfo['value'] ) ) {
787 $descriptor['default'] = $singleFieldInfo['value'];
788 }
789
790 if ( empty( $singleFieldInfo['optional'] ) ) {
791 $descriptor['required'] = true;
792 }
793 }
794
795 return $descriptor;
796 }
797
804 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
805 $i = 0;
806 foreach ( $formDescriptor as &$field ) {
807 $field['__index'] = $i++;
808 }
809 unset( $field );
810 uasort( $formDescriptor, static function ( $first, $second ) {
811 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
812 ?: $first['__index'] <=> $second['__index'];
813 } );
814 foreach ( $formDescriptor as &$field ) {
815 unset( $field['__index'] );
816 }
817 }
818
826 protected static function getField( array $array, $fieldName, $default = null ) {
827 if ( array_key_exists( $fieldName, $array ) ) {
828 return $array[$fieldName];
829 } else {
830 return $default;
831 }
832 }
833
841 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
842 $map = [
843 'string' => 'text',
844 'password' => 'password',
845 'select' => 'select',
846 'checkbox' => 'check',
847 'multiselect' => 'multiselect',
848 'button' => 'submit',
849 'hidden' => 'hidden',
850 'null' => 'info',
851 ];
852 if ( !array_key_exists( $type, $map ) ) {
853 throw new InvalidArgumentException( 'invalid field type: ' . $type );
854 }
855 return $map[$type];
856 }
857
870 protected static function mergeDefaultFormDescriptor(
871 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
872 ) {
873 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
874 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
875 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
876 // to something in the fieldinfo, and is not an info field or a submit button
877 if (
878 !isset( $fieldInfo[$fieldName] )
879 && (
880 !isset( $defaultField['baseField'] )
881 || !isset( $fieldInfo[$defaultField['baseField']] )
882 )
883 && (
884 !isset( $defaultField['type'] )
885 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
886 )
887 ) {
888 $defaultFormDescriptor[$fieldName] = null;
889 continue;
890 }
891
892 // default message labels should always take priority
893 $requestField = $formDescriptor[$fieldName] ?? [];
894 if (
895 isset( $defaultField['label'] )
896 || isset( $defaultField['label-message'] )
897 || isset( $defaultField['label-raw'] )
898 ) {
899 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
900 }
901
902 $defaultFormDescriptor[$fieldName] += $requestField;
903 }
904
905 return array_filter( $defaultFormDescriptor + $formDescriptor );
906 }
907}
908
910class_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:208
Variant of the Message class.
Create PSR-3 logger objects.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:158
Similar to MediaWiki\Request\FauxRequest, but only fakes URL parameters and method (POST or GET) and ...
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form,...
Value object representing a CSRF token.
Definition Token.php: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.