MediaWiki master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
4
5use InvalidArgumentException;
6use LogicException;
23use MWCryptRand;
24use Profiler;
25use StatusValue;
26use UnexpectedValueException;
27
38abstract class AuthManagerSpecialPage extends SpecialPage {
42 protected static $allowedActions = [
43 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
44 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
45 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
46 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
47 ];
48
50 protected static $messages = [];
51
53 protected $authAction;
54
56 protected $authRequests;
57
59 protected $subPage;
60
62 protected $isReturn;
63
65 protected $savedRequest;
66
68 protected $isFakePostRequest = false;
69
82 public function onAuthChangeFormFields(
83 array $requests, array $fieldInfo, array &$formDescriptor, $action
84 ) {
85 }
86
91 protected function getLoginSecurityLevel() {
92 return $this->getName();
93 }
94
96 public function getRequest() {
97 return $this->savedRequest ?: $this->getContext()->getRequest();
98 }
99
110 protected function setRequest( array $data, $wasPosted = null ) {
111 $request = $this->getContext()->getRequest();
112 $this->isFakePostRequest = $wasPosted === true && $request->wasPosted() === false;
113 $this->savedRequest = new DerivativeRequest(
114 $request,
115 $data + $request->getQueryValues(),
116 $wasPosted ?? $request->wasPosted()
117 );
118 }
119
121 protected function beforeExecute( $subPage ) {
122 $this->getOutput()->disallowUserJs();
123
124 return $this->handleReturnBeforeExecute( $subPage )
125 && $this->handleReauthBeforeExecute( $subPage );
126 }
127
144 protected function handleReturnBeforeExecute( $subPage ) {
145 $authManager = $this->getAuthManager();
146 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
147
148 if ( $subPage === 'return' ) {
149 $this->loadAuth( $subPage );
150 $preservedParams = $this->getPreservedParams();
151
152 // FIXME save POST values only from request
153 $authData = array_diff_key( $this->getRequest()->getValues(),
154 $preservedParams, [ 'title' => 1 ] );
155 $uniqueId = MWCryptRand::generateHex( 6 );
156 $preservedParams['authUniqueId'] = $uniqueId;
157 $key .= ':' . $uniqueId;
158 $authManager->setAuthenticationSessionData( $key, $authData );
159
160 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
161 $this->getOutput()->redirect( $url );
162 return false;
163 } elseif ( $this->getRequest()->getCheck( 'authUniqueId' ) ) {
164 $uniqueId = $this->getRequest()->getVal( 'authUniqueId' );
165 $key .= ':' . $uniqueId;
166 $authData = $authManager->getAuthenticationSessionData( $key );
167 if ( $authData ) {
168 $authManager->removeAuthenticationSessionData( $key );
169 $this->isReturn = true;
170 $this->setRequest( $authData, true );
171 $this->setPostTransactionProfilerExpectations( __METHOD__ );
172 }
173 }
174
175 return true;
176 }
177
188 protected function handleReauthBeforeExecute( $subPage ) {
189 $authManager = $this->getAuthManager();
190 $request = $this->getRequest();
191 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
192
193 $securityLevel = $this->getLoginSecurityLevel();
194 if ( $securityLevel ) {
195 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
196 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
197 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
198
199 if ( $request->wasPosted() ) {
200 // unique ID in case the same special page is open in multiple browser tabs
201 $uniqueId = MWCryptRand::generateHex( 6 );
202 $key .= ':' . $uniqueId;
203
204 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
205 $authData = array_diff_key( $request->getValues(),
206 $this->getPreservedParams(), [ 'title' => 1 ] );
207 $authManager->setAuthenticationSessionData( $key, $authData );
208 }
209
210 // Copied from RedirectSpecialPage::getRedirectQuery()
211 // Would using $this->getPreservedParams() be appropriate here?
212 $keepParams = [ 'uselang', 'useskin', 'useformat', 'variant', 'debug', 'safemode' ];
213
214 $title = SpecialPage::getTitleFor( 'Userlogin' );
215 $url = $title->getFullURL( [
216 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
217 'returntoquery' => wfArrayToCgi( $queryParams ),
218 'force' => $securityLevel,
219 ] + array_intersect_key( $queryParams, array_fill_keys( $keepParams, true ) ), false, PROTO_HTTPS );
220
221 $this->getOutput()->redirect( $url );
222 return false;
223 }
224
225 if ( $securityStatus !== AuthManager::SEC_OK ) {
226 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
227 }
228 }
229
230 $uniqueId = $request->getVal( 'authUniqueId' );
231 if ( $uniqueId ) {
232 $key .= ':' . $uniqueId;
233 $authData = $authManager->getAuthenticationSessionData( $key );
234 if ( $authData ) {
235 $authManager->removeAuthenticationSessionData( $key );
236 $this->setRequest( $authData, true );
237 $this->setPostTransactionProfilerExpectations( __METHOD__ );
238 }
239 }
240
241 return true;
242 }
243
244 private function setPostTransactionProfilerExpectations( string $fname ) {
245 $trxLimits = $this->getConfig()->get( MainConfigNames::TrxProfilerLimits );
246 $trxProfiler = Profiler::instance()->getTransactionProfiler();
247 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
248 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname ) {
249 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
250 } );
251 }
252
260 abstract protected function getDefaultAction( $subPage );
261
268 protected function messageKey( $defaultKey ) {
269 return array_key_exists( $defaultKey, static::$messages )
270 ? static::$messages[$defaultKey] : $defaultKey;
271 }
272
278 protected function getRequestBlacklist() {
279 return [];
280 }
281
292 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
293 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
294 // calls. This is important for requests which have hidden information, so any
295 // getAuthenticationRequests call would mean putting data into some cache.
296 if (
297 !$reset && $this->subPage === $subPage && $this->authAction
298 && ( !$authAction || $authAction === $this->authAction )
299 ) {
300 return;
301 }
302
303 $request = $this->getRequest();
304 $this->subPage = $subPage;
305 $this->authAction = $authAction ?: $request->getText( 'authAction' );
306 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
307 $this->authAction = $this->getDefaultAction( $subPage );
308 if ( $request->wasPosted() ) {
309 $continueAction = $this->getContinueAction( $this->authAction );
310 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
311 $this->authAction = $continueAction;
312 }
313 }
314 }
315
316 $allReqs = $this->getAuthManager()->getAuthenticationRequests(
317 $this->authAction, $this->getUser() );
318 $this->authRequests = array_filter( $allReqs, function ( $req ) {
319 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
320 } );
321 }
322
327 protected function isContinued() {
328 return in_array( $this->authAction, [
329 AuthManager::ACTION_LOGIN_CONTINUE,
330 AuthManager::ACTION_CREATE_CONTINUE,
331 AuthManager::ACTION_LINK_CONTINUE,
332 ], true );
333 }
334
340 protected function getContinueAction( $action ) {
341 switch ( $action ) {
342 case AuthManager::ACTION_LOGIN:
343 $action = AuthManager::ACTION_LOGIN_CONTINUE;
344 break;
345 case AuthManager::ACTION_CREATE:
346 $action = AuthManager::ACTION_CREATE_CONTINUE;
347 break;
348 case AuthManager::ACTION_LINK:
349 $action = AuthManager::ACTION_LINK_CONTINUE;
350 break;
351 }
352 return $action;
353 }
354
362 protected function isActionAllowed( $action ) {
363 $authManager = $this->getAuthManager();
364 if ( !in_array( $action, static::$allowedActions, true ) ) {
365 throw new InvalidArgumentException( 'invalid action: ' . $action );
366 }
367
368 // calling getAuthenticationRequests can be expensive, avoid if possible
369 $requests = ( $action === $this->authAction ) ? $this->authRequests
370 : $authManager->getAuthenticationRequests( $action );
371 if ( !$requests ) {
372 // no provider supports this action in the current state
373 return false;
374 }
375
376 switch ( $action ) {
377 case AuthManager::ACTION_LOGIN:
378 case AuthManager::ACTION_LOGIN_CONTINUE:
379 return $authManager->canAuthenticateNow();
380 case AuthManager::ACTION_CREATE:
381 case AuthManager::ACTION_CREATE_CONTINUE:
382 return $authManager->canCreateAccounts();
383 case AuthManager::ACTION_LINK:
384 case AuthManager::ACTION_LINK_CONTINUE:
385 return $authManager->canLinkAccounts();
386 case AuthManager::ACTION_CHANGE:
387 case AuthManager::ACTION_REMOVE:
388 case AuthManager::ACTION_UNLINK:
389 return true;
390 default:
391 // should never reach here but makes static code analyzers happy
392 throw new InvalidArgumentException( 'invalid action: ' . $action );
393 }
394 }
395
402 protected function performAuthenticationStep( $action, array $requests ) {
403 if ( !in_array( $action, static::$allowedActions, true ) ) {
404 throw new InvalidArgumentException( 'invalid action: ' . $action );
405 }
406
407 $authManager = $this->getAuthManager();
408 $returnToUrl = $this->getPageTitle( 'return' )
409 ->getFullURL( $this->getPreservedParams( [ 'withToken' => true ] ), false, PROTO_HTTPS );
410
411 switch ( $action ) {
412 case AuthManager::ACTION_LOGIN:
413 return $authManager->beginAuthentication( $requests, $returnToUrl );
414 case AuthManager::ACTION_LOGIN_CONTINUE:
415 return $authManager->continueAuthentication( $requests );
416 case AuthManager::ACTION_CREATE:
417 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
418 $returnToUrl );
419 case AuthManager::ACTION_CREATE_CONTINUE:
420 return $authManager->continueAccountCreation( $requests );
421 case AuthManager::ACTION_LINK:
422 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
423 case AuthManager::ACTION_LINK_CONTINUE:
424 return $authManager->continueAccountLink( $requests );
425 case AuthManager::ACTION_CHANGE:
426 case AuthManager::ACTION_REMOVE:
427 case AuthManager::ACTION_UNLINK:
428 if ( count( $requests ) > 1 ) {
429 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
430 }
431
432 if ( !$requests ) {
433 throw new InvalidArgumentException( 'no auth request' );
434 }
435 $req = reset( $requests );
436 $status = $authManager->allowsAuthenticationDataChange( $req );
437 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
438 if ( !$status->isGood() ) {
439 return AuthenticationResponse::newFail( $status->getMessage() );
440 }
441 $authManager->changeAuthenticationData( $req );
442 return AuthenticationResponse::newPass();
443 default:
444 // should never reach here but makes static code analyzers happy
445 throw new InvalidArgumentException( 'invalid action: ' . $action );
446 }
447 }
448
459 protected function trySubmit() {
460 $status = false;
461
462 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
463 $form->setSubmitCallback( $this->handleFormSubmit( ... ) );
464
465 if ( $this->getRequest()->wasPosted() ) {
466 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
467 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
468 $sessionToken = $this->getToken();
469 if ( $sessionToken->wasNew() && ( !$this->isFakePostRequest || $this->isReturn ) ) {
470 // TODO: This should use `$this->getRequest()->getSession()->getProvider()->whyNoSession()`.
471 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
472 } elseif ( !$requestTokenValue ) {
473 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
474 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
475 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
476 }
477
478 $form->prepareForm();
479 $status = $form->trySubmit();
480
481 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
482 // FIXME this probably should be in HTMLForm
483 if ( $status === true ) {
484 // not supposed to happen since our submit handler should always return a Status
485 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
486 } elseif ( $status === false ) {
487 // form was not submitted; nothing to do
488 } elseif ( $status instanceof Status ) {
489 // already handled by the form; nothing to do
490 } elseif ( $status instanceof StatusValue ) {
491 // in theory not an allowed return type but nothing stops the submit handler from
492 // accidentally returning it so best check and fix
493 $status = Status::wrap( $status );
494 } elseif ( is_string( $status ) ) {
495 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
496 } elseif ( is_array( $status ) ) {
497 if ( is_string( reset( $status ) ) ) {
498 // @phan-suppress-next-line PhanParamTooFewUnpack
499 $status = Status::newFatal( ...$status );
500 } elseif ( is_array( reset( $status ) ) ) {
501 $ret = Status::newGood();
502 foreach ( $status as $message ) {
503 // @phan-suppress-next-line PhanParamTooFewUnpack
504 $ret->fatal( ...$message );
505 }
506 $status = $ret;
507 } else {
508 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
509 . 'first element of array is ' . get_debug_type( reset( $status ) ) );
510 }
511 } else {
512 // not supposed to happen, but HTMLForm does not verify the return type
513 // from the submit callback; better safe then sorry!
514 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
515 . get_debug_type( $status ) );
516 }
517
518 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
519 // This is awkward. There was a form validation error, which means the data was not
520 // passed to AuthManager. Normally we would display the form with an error message,
521 // but for the data we received via the redirect flow that would not be helpful at all.
522 // Let's just submit the data to AuthManager directly instead.
523 LoggerFactory::getInstance( 'authentication' )
524 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
525 'status' => $status->getWikiText( false, false, 'en' ) ] );
526 $status = $this->handleFormSubmit( $form->mFieldData );
527 }
528 }
529
530 $changeActions = [
531 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
532 ];
533 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
534 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
535 }
536
537 return $status;
538 }
539
546 public function handleFormSubmit( $data ) {
547 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
548 $response = $this->performAuthenticationStep( $this->authAction, $requests );
549
550 // we can't handle FAIL or similar as failure here since it might require changing the form
551 return Status::newGood( $response );
552 }
553
574 protected function getPreservedParams( $options = [] ) {
575 if ( is_bool( $options ) ) {
576 wfDeprecated( __METHOD__ . ' boolean $options', '1.43' );
577 $options = [ 'withToken' => $options ];
578 }
579 $options += [
580 'reset' => false,
581 'withToken' => false,
582 ];
583 $params = [];
584 $request = $this->getRequest();
585
586 $params += [
587 'uselang' => $request->getVal( 'uselang' ),
588 'variant' => $request->getVal( 'variant' ),
589 'returnto' => $request->getVal( 'returnto' ),
590 'returntoquery' => $request->getVal( 'returntoquery' ),
591 'returntoanchor' => $request->getVal( 'returntoanchor' ),
592 ];
593
594 if ( !$options['reset'] && $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
595 $params['authAction'] = $this->getContinueAction( $this->authAction );
596 }
597
598 if ( $options['withToken'] ) {
599 $params[$this->getTokenName()] = $this->getToken()->toString();
600 }
601
602 // Allow authentication extensions like CentralAuth to preserve their own
603 // query params during and after the authentication process.
604 $this->getHookRunner()->onAuthPreserveQueryParams(
605 $params, [ 'request' => $request, 'reset' => $options['reset'] ]
606 );
607
608 return array_filter( $params, static fn ( $val ) => $val !== null );
609 }
610
618 protected function getAuthFormDescriptor( $requests, $action ) {
619 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
620 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
621
622 $this->addTabIndex( $formDescriptor );
623
624 return $formDescriptor;
625 }
626
633 protected function getAuthForm( array $requests, $action ) {
634 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
635 $context = $this->getContext();
636 if ( $context->getRequest() !== $this->getRequest() ) {
637 // We have overridden the request, need to make sure the form uses that too.
638 $context = new DerivativeContext( $this->getContext() );
639 $context->setRequest( $this->getRequest() );
640 }
641 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
642 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
643 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
644 $form->addHiddenField( 'authAction', $this->authAction );
645 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
646
647 return $form;
648 }
649
654 protected function displayForm( $status ) {
655 if ( $status instanceof StatusValue ) {
656 $status = Status::wrap( $status );
657 }
658 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
659 $form->prepareForm()->displayForm( $status );
660 }
661
673 protected function needsSubmitButton( array $requests ) {
674 $customSubmitButtonPresent = false;
675
676 // Secondary and preauth providers always need their data; they will not care what button
677 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
678 // that's the point in being optional. Se we need to check whether all primary providers
679 // have their own buttons and whether there is at least one button present.
680 foreach ( $requests as $req ) {
681 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
682 if ( $this->hasOwnSubmitButton( $req ) ) {
683 $customSubmitButtonPresent = true;
684 } else {
685 return true;
686 }
687 }
688 }
689 return !$customSubmitButtonPresent;
690 }
691
697 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
698 foreach ( $req->getFieldInfo() as $info ) {
699 if ( $info['type'] === 'button' ) {
700 return true;
701 }
702 }
703 return false;
704 }
705
711 protected function addTabIndex( &$formDescriptor ) {
712 $i = 1;
713 foreach ( $formDescriptor as &$definition ) {
714 $class = false;
715 if ( array_key_exists( 'class', $definition ) ) {
716 $class = $definition['class'];
717 } elseif ( array_key_exists( 'type', $definition ) ) {
718 $class = HTMLForm::$typeMappings[$definition['type']];
719 }
720 if ( $class !== HTMLInfoField::class ) {
721 $definition['tabindex'] = $i;
722 $i++;
723 }
724 }
725 }
726
732 protected function getToken() {
733 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
734 . $this->getName() );
735 }
736
742 protected function getTokenName() {
743 return 'wpAuthToken';
744 }
745
755 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
756 $formDescriptor = [];
757 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
758 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
759 }
760
761 $requestSnapshot = serialize( $requests );
762 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
763 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
764 $formDescriptor, $action );
765 if ( $requestSnapshot !== serialize( $requests ) ) {
766 LoggerFactory::getInstance( 'authentication' )->warning(
767 'AuthChangeFormFields hook changed auth requests' );
768 }
769
770 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
771 // subscribers (who only see one field at a time) to influence ordering.
772 self::sortFormDescriptorFields( $formDescriptor );
773
774 return $formDescriptor;
775 }
776
784 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
785 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
786 $descriptor = [
787 'type' => $type,
788 // Do not prefix input name with 'wp'. This is important for the redirect flow.
789 'name' => $fieldName,
790 ];
791
792 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
793 $descriptor['default'] = $singleFieldInfo['label']->plain();
794 } elseif ( $type !== 'submit' ) {
795 $descriptor += array_filter( [
796 // help-message is omitted as it is usually not really useful for a web interface
797 'label-message' => self::getField( $singleFieldInfo, 'label' ),
798 ] );
799
800 if ( isset( $singleFieldInfo['options'] ) ) {
801 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
803 return $message->parse();
804 }, $singleFieldInfo['options'] ) );
805 }
806
807 if ( isset( $singleFieldInfo['value'] ) ) {
808 $descriptor['default'] = $singleFieldInfo['value'];
809 }
810
811 if ( empty( $singleFieldInfo['optional'] ) ) {
812 $descriptor['required'] = true;
813 }
814 }
815
816 return $descriptor;
817 }
818
824 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
825 $i = 0;
826 foreach ( $formDescriptor as &$field ) {
827 $field['__index'] = $i++;
828 }
829 unset( $field );
830 uasort( $formDescriptor, static function ( $first, $second ) {
831 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
832 ?: $first['__index'] <=> $second['__index'];
833 } );
834 foreach ( $formDescriptor as &$field ) {
835 unset( $field['__index'] );
836 }
837 }
838
846 protected static function getField( array $array, $fieldName, $default = null ) {
847 if ( array_key_exists( $fieldName, $array ) ) {
848 return $array[$fieldName];
849 } else {
850 return $default;
851 }
852 }
853
861 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
862 $map = [
863 'string' => 'text',
864 'password' => 'password',
865 'select' => 'select',
866 'checkbox' => 'check',
867 'multiselect' => 'multiselect',
868 'button' => 'submit',
869 'hidden' => 'hidden',
870 'null' => 'info',
871 ];
872 if ( !array_key_exists( $type, $map ) ) {
873 throw new InvalidArgumentException( 'invalid field type: ' . $type );
874 }
875 return $map[$type];
876 }
877
890 protected static function mergeDefaultFormDescriptor(
891 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
892 ) {
893 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
894 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
895 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
896 // to something in the fieldinfo, and is not an info field or a submit button
897 if (
898 !isset( $fieldInfo[$fieldName] )
899 && (
900 !isset( $defaultField['baseField'] )
901 || !isset( $fieldInfo[$defaultField['baseField']] )
902 )
903 && (
904 !isset( $defaultField['type'] )
905 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
906 )
907 ) {
908 $defaultFormDescriptor[$fieldName] = null;
909 continue;
910 }
911
912 // default message labels should always take priority
913 $requestField = $formDescriptor[$fieldName] ?? [];
914 if (
915 isset( $defaultField['label'] )
916 || isset( $defaultField['label-message'] )
917 || isset( $defaultField['label-raw'] )
918 ) {
919 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
920 }
921
922 $defaultFormDescriptor[$fieldName] += $requestField;
923 }
924
925 return array_filter( $defaultFormDescriptor + $formDescriptor );
926 }
927}
928
930class_alias( AuthManagerSpecialPage::class, 'AuthManagerSpecialPage' );
const PROTO_HTTPS
Definition Defines.php:218
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.
A cryptographic random generator class used for generating secret keys.
AuthManager is the authentication system in MediaWiki and serves entry point for authentication.
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 ...
Defer callable updates to run later in the PHP process.
An error page which can definitely be safely rendered using the OutputPage.
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:195
Variant of the Message class.
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const TrxProfilerLimits
Name constant for the TrxProfilerLimits setting, for use with Config::get()
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
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:17
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.
beforeExecute( $subPage)
Gets called before execute.Return false to prevent calling execute() (since 1.27+)....
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.
bool $isFakePostRequest
Set when we're pretending that we got a POST request during redirect flows.
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 1.18
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.
getConfig()
Shortcut to get main config 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:44
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:22
static instance()
Definition Profiler.php:90
Generic operation result class Has warning/error list, boolean status and arbitrary value.