MediaWiki master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1<?php
2
4
5use InvalidArgumentException;
6use LogicException;
27use StatusValue;
28use UnexpectedValueException;
29
40abstract class AuthManagerSpecialPage extends SpecialPage {
44 protected static $allowedActions = [
45 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
46 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
47 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
48 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
49 ];
50
52 protected static $messages = [];
53
55 protected $authAction;
56
58 protected $authRequests;
59
61 protected $subPage;
62
64 protected $isReturn;
65
67 protected $savedRequest;
68
70 protected $isFakePostRequest = false;
71
84 public function onAuthChangeFormFields(
85 array $requests, array $fieldInfo, array &$formDescriptor, $action
86 ) {
87 }
88
93 protected function getLoginSecurityLevel() {
94 return $this->getName();
95 }
96
98 public function getRequest() {
99 return $this->savedRequest ?: $this->getContext()->getRequest();
100 }
101
112 protected function setRequest( array $data, $wasPosted = null ) {
113 $request = $this->getContext()->getRequest();
114 $this->isFakePostRequest = $wasPosted === true && $request->wasPosted() === false;
115 $this->savedRequest = new DerivativeRequest(
116 $request,
117 $data + $request->getQueryValues(),
118 $wasPosted ?? $request->wasPosted()
119 );
120 }
121
123 protected function beforeExecute( $subPage ) {
124 $this->getOutput()->disallowUserJs();
125
126 return $this->handleReturnBeforeExecute( $subPage )
127 && $this->handleReauthBeforeExecute( $subPage );
128 }
129
146 protected function handleReturnBeforeExecute( $subPage ) {
147 $authManager = $this->getAuthManager();
148 $key = 'AuthManagerSpecialPage:return:' . $this->getName();
149
150 if ( $subPage === 'return' ) {
151 $this->loadAuth( $subPage );
152 $preservedParams = $this->getPreservedParams();
153
154 // FIXME save POST values only from request
155 $authData = array_diff_key( $this->getRequest()->getValues(),
156 $preservedParams, [ 'title' => 1 ] );
157 $uniqueId = MWCryptRand::generateHex( 6 );
158 $preservedParams['authUniqueId'] = $uniqueId;
159 $key .= ':' . $uniqueId;
160 $authManager->setAuthenticationSessionData( $key, $authData );
161
162 $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
163 $this->getOutput()->redirect( $url );
164 return false;
165 } elseif ( $this->getRequest()->getCheck( 'authUniqueId' ) ) {
166 $uniqueId = $this->getRequest()->getVal( 'authUniqueId' );
167 $key .= ':' . $uniqueId;
168 $authData = $authManager->getAuthenticationSessionData( $key );
169 if ( $authData ) {
170 $authManager->removeAuthenticationSessionData( $key );
171 $this->isReturn = true;
172 $this->setRequest( $authData, true );
173 $this->setPostTransactionProfilerExpectations( __METHOD__ );
174 }
175 }
176
177 return true;
178 }
179
190 protected function handleReauthBeforeExecute( $subPage ) {
191 $authManager = $this->getAuthManager();
192 $request = $this->getRequest();
193 $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
194
195 $securityLevel = $this->getLoginSecurityLevel();
196 if ( $securityLevel ) {
197 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
198 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
199 $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
200
201 if ( $request->wasPosted() ) {
202 // unique ID in case the same special page is open in multiple browser tabs
203 $uniqueId = MWCryptRand::generateHex( 6 );
204 $key .= ':' . $uniqueId;
205
206 $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
207 $authData = array_diff_key( $request->getValues(),
208 $this->getPreservedParams(), [ 'title' => 1 ] );
209 $authManager->setAuthenticationSessionData( $key, $authData );
210 }
211
212 // Copied from RedirectSpecialPage::getRedirectQuery()
213 // Would using $this->getPreservedParams() be appropriate here?
214 $keepParams = [ 'uselang', 'useskin', 'useformat', 'variant', 'debug', 'safemode' ];
215
216 $title = SpecialPage::getTitleFor( 'Userlogin' );
217 $url = $title->getFullURL( [
218 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
219 'returntoquery' => wfArrayToCgi( $queryParams ),
220 'force' => $securityLevel,
221 ] + array_intersect_key( $queryParams, array_fill_keys( $keepParams, true ) ), false, PROTO_HTTPS );
222
223 $this->getOutput()->redirect( $url );
224 return false;
225 }
226
227 if ( $securityStatus !== AuthManager::SEC_OK ) {
228 throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
229 }
230 }
231
232 $uniqueId = $request->getVal( 'authUniqueId' );
233 if ( $uniqueId ) {
234 $key .= ':' . $uniqueId;
235 $authData = $authManager->getAuthenticationSessionData( $key );
236 if ( $authData ) {
237 $authManager->removeAuthenticationSessionData( $key );
238 $this->setRequest( $authData, true );
239 $this->setPostTransactionProfilerExpectations( __METHOD__ );
240 }
241 }
242
243 return true;
244 }
245
246 private function setPostTransactionProfilerExpectations( string $fname ) {
247 $trxLimits = $this->getConfig()->get( MainConfigNames::TrxProfilerLimits );
248 $trxProfiler = Profiler::instance()->getTransactionProfiler();
249 $trxProfiler->redefineExpectations( $trxLimits['POST'], $fname );
250 DeferredUpdates::addCallableUpdate( static function () use ( $trxProfiler, $trxLimits, $fname ) {
251 $trxProfiler->redefineExpectations( $trxLimits['PostSend-POST'], $fname );
252 } );
253 }
254
262 abstract protected function getDefaultAction( $subPage );
263
270 protected function messageKey( $defaultKey ) {
271 return array_key_exists( $defaultKey, static::$messages )
272 ? static::$messages[$defaultKey] : $defaultKey;
273 }
274
280 protected function getRequestBlacklist() {
281 return [];
282 }
283
296 protected function getAuthenticationRequests( $action, ?UserIdentity $user = null ) {
297 return $this->getAuthManager()->getAuthenticationRequests( $action,
298 $user );
299 }
300
311 protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
312 // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
313 // calls. This is important for requests which have hidden information, so any
314 // getAuthenticationRequests call would mean putting data into some cache.
315 if (
316 !$reset && $this->subPage === $subPage && $this->authAction
317 && ( !$authAction || $authAction === $this->authAction )
318 ) {
319 return;
320 }
321
322 $request = $this->getRequest();
323 $this->subPage = $subPage;
324 $this->authAction = $authAction ?: $request->getText( 'authAction' );
325 if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
326 $this->authAction = $this->getDefaultAction( $subPage );
327 if ( $request->wasPosted() ) {
328 $continueAction = $this->getContinueAction( $this->authAction );
329 if ( in_array( $continueAction, static::$allowedActions, true ) ) {
330 $this->authAction = $continueAction;
331 }
332 }
333 }
334
335 $allReqs = $this->getAuthenticationRequests( $this->authAction, $this->getUser() );
336 $this->authRequests = array_filter( $allReqs, function ( $req ) {
337 return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
338 } );
339 }
340
345 protected function isContinued() {
346 return in_array( $this->authAction, [
347 AuthManager::ACTION_LOGIN_CONTINUE,
348 AuthManager::ACTION_CREATE_CONTINUE,
349 AuthManager::ACTION_LINK_CONTINUE,
350 ], true );
351 }
352
358 protected function getContinueAction( $action ) {
359 switch ( $action ) {
360 case AuthManager::ACTION_LOGIN:
361 $action = AuthManager::ACTION_LOGIN_CONTINUE;
362 break;
363 case AuthManager::ACTION_CREATE:
364 $action = AuthManager::ACTION_CREATE_CONTINUE;
365 break;
366 case AuthManager::ACTION_LINK:
367 $action = AuthManager::ACTION_LINK_CONTINUE;
368 break;
369 }
370 return $action;
371 }
372
380 protected function isActionAllowed( $action ) {
381 $authManager = $this->getAuthManager();
382 if ( !in_array( $action, static::$allowedActions, true ) ) {
383 throw new InvalidArgumentException( 'invalid action: ' . $action );
384 }
385
386 // calling getAuthenticationRequests can be expensive, avoid if possible
387 $requests = ( $action === $this->authAction ) ? $this->authRequests
388 : $this->getAuthenticationRequests( $action );
389 if ( !$requests ) {
390 // no provider supports this action in the current state
391 return false;
392 }
393
394 switch ( $action ) {
395 case AuthManager::ACTION_LOGIN:
396 case AuthManager::ACTION_LOGIN_CONTINUE:
397 return $authManager->canAuthenticateNow();
398 case AuthManager::ACTION_CREATE:
399 case AuthManager::ACTION_CREATE_CONTINUE:
400 return $authManager->canCreateAccounts();
401 case AuthManager::ACTION_LINK:
402 case AuthManager::ACTION_LINK_CONTINUE:
403 return $authManager->canLinkAccounts();
404 case AuthManager::ACTION_CHANGE:
405 case AuthManager::ACTION_REMOVE:
406 case AuthManager::ACTION_UNLINK:
407 return true;
408 default:
409 // should never reach here but makes static code analyzers happy
410 throw new InvalidArgumentException( 'invalid action: ' . $action );
411 }
412 }
413
420 protected function performAuthenticationStep( $action, array $requests ) {
421 if ( !in_array( $action, static::$allowedActions, true ) ) {
422 throw new InvalidArgumentException( 'invalid action: ' . $action );
423 }
424
425 $authManager = $this->getAuthManager();
426 $returnToUrl = $this->getPageTitle( 'return' )
427 ->getFullURL( $this->getPreservedParams( [ 'withToken' => true ] ), false, PROTO_HTTPS );
428
429 switch ( $action ) {
430 case AuthManager::ACTION_LOGIN:
431 return $authManager->beginAuthentication( $requests, $returnToUrl );
432 case AuthManager::ACTION_LOGIN_CONTINUE:
433 return $authManager->continueAuthentication( $requests );
434 case AuthManager::ACTION_CREATE:
435 return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
436 $returnToUrl );
437 case AuthManager::ACTION_CREATE_CONTINUE:
438 return $authManager->continueAccountCreation( $requests );
439 case AuthManager::ACTION_LINK:
440 return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
441 case AuthManager::ACTION_LINK_CONTINUE:
442 return $authManager->continueAccountLink( $requests );
443 case AuthManager::ACTION_CHANGE:
444 case AuthManager::ACTION_REMOVE:
445 case AuthManager::ACTION_UNLINK:
446 if ( count( $requests ) > 1 ) {
447 throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
448 }
449
450 if ( !$requests ) {
451 throw new InvalidArgumentException( 'no auth request' );
452 }
453 $req = reset( $requests );
454 $status = $authManager->allowsAuthenticationDataChange( $req );
455 $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
456 if ( !$status->isGood() ) {
457 return AuthenticationResponse::newFail( $status->getMessage() );
458 }
459 $authManager->changeAuthenticationData( $req );
460 return AuthenticationResponse::newPass();
461 default:
462 // should never reach here but makes static code analyzers happy
463 throw new InvalidArgumentException( 'invalid action: ' . $action );
464 }
465 }
466
477 protected function trySubmit() {
478 $status = false;
479
480 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
481 $form->setSubmitCallback( $this->handleFormSubmit( ... ) );
482
483 if ( $this->getRequest()->wasPosted() ) {
484 // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
485 $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
486 $sessionToken = $this->getToken();
487 if ( $sessionToken->wasNew() && ( !$this->isFakePostRequest || $this->isReturn ) ) {
488 // TODO: This should use `$this->getRequest()->getSession()->getProvider()->whyNoSession()`.
489 return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
490 } elseif ( !$requestTokenValue ) {
491 return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
492 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
493 return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
494 }
495
496 $form->prepareForm();
497 $status = $form->trySubmit();
498
499 // HTMLForm submit return values are a mess; let's ensure it is false or a Status
500 // FIXME this probably should be in HTMLForm
501 if ( $status === true ) {
502 // not supposed to happen since our submit handler should always return a Status
503 throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
504 } elseif ( $status === false ) {
505 // form was not submitted; nothing to do
506 } elseif ( $status instanceof Status ) {
507 // already handled by the form; nothing to do
508 } elseif ( $status instanceof StatusValue ) {
509 // in theory not an allowed return type but nothing stops the submit handler from
510 // accidentally returning it so best check and fix
511 $status = Status::wrap( $status );
512 } elseif ( is_string( $status ) ) {
513 $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
514 } elseif ( is_array( $status ) ) {
515 if ( is_string( reset( $status ) ) ) {
516 // @phan-suppress-next-line PhanParamTooFewUnpack
517 $status = Status::newFatal( ...$status );
518 } elseif ( is_array( reset( $status ) ) ) {
519 $ret = Status::newGood();
520 foreach ( $status as $message ) {
521 // @phan-suppress-next-line PhanParamTooFewUnpack
522 $ret->fatal( ...$message );
523 }
524 $status = $ret;
525 } else {
526 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
527 . 'first element of array is ' . get_debug_type( reset( $status ) ) );
528 }
529 } else {
530 // not supposed to happen, but HTMLForm does not verify the return type
531 // from the submit callback; better safe then sorry!
532 throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
533 . get_debug_type( $status ) );
534 }
535
536 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
537 // This is awkward. There was a form validation error, which means the data was not
538 // passed to AuthManager. Normally we would display the form with an error message,
539 // but for the data we received via the redirect flow that would not be helpful at all.
540 // Let's just submit the data to AuthManager directly instead.
541 LoggerFactory::getInstance( 'authentication' )
542 ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
543 'status' => $status->getWikiText( false, false, 'en' ) ] );
544 $status = $this->handleFormSubmit( $form->mFieldData );
545 }
546 }
547
548 $changeActions = [
549 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
550 ];
551 if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
552 $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
553 }
554
555 return $status;
556 }
557
564 public function handleFormSubmit( $data ) {
565 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
566 $response = $this->performAuthenticationStep( $this->authAction, $requests );
567
568 // we can't handle FAIL or similar as failure here since it might require changing the form
569 return Status::newGood( $response );
570 }
571
593 protected function getPreservedParams( $options = [] ) {
594 if ( is_bool( $options ) ) {
595 wfDeprecated( __METHOD__ . ' boolean $options', '1.43' );
596 $options = [ 'withToken' => $options ];
597 }
598 $options += [
599 'reset' => false,
600 'withToken' => false,
601 ];
602
603 $params = $options['params'] ?? [];
604 if ( !$options['reset'] && $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
605 $params['authAction'] = $this->getContinueAction( $this->authAction );
606 }
607 if ( $options['withToken'] ) {
608 $params[$this->getTokenName()] = $this->getToken()->toString();
609 }
610
611 $loginHelper = new LoginHelper( $this->getContext() );
612 return $loginHelper->getPreservedParams( [ 'reset' => $options['reset'], 'params' => $params ] );
613 }
614
622 protected function getAuthFormDescriptor( $requests, $action ) {
623 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
624 $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
625
626 $this->addTabIndex( $formDescriptor );
627
628 return $formDescriptor;
629 }
630
637 protected function getAuthForm( array $requests, $action ) {
638 $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
639 $context = $this->getContext();
640 if ( $context->getRequest() !== $this->getRequest() ) {
641 // We have overridden the request, need to make sure the form uses that too.
642 $context = new DerivativeContext( $this->getContext() );
643 $context->setRequest( $this->getRequest() );
644 }
645 $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
646 $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
647 $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
648 $form->addHiddenField( 'authAction', $this->authAction );
649 $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
650
651 return $form;
652 }
653
658 protected function displayForm( $status ) {
659 if ( $status instanceof StatusValue ) {
660 $status = Status::wrap( $status );
661 }
662 $form = $this->getAuthForm( $this->authRequests, $this->authAction );
663 $form->prepareForm()->displayForm( $status );
664 }
665
677 protected function needsSubmitButton( array $requests ) {
678 $customSubmitButtonPresent = false;
679
680 // Secondary and preauth providers always need their data; they will not care what button
681 // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
682 // that's the point in being optional. Se we need to check whether all primary providers
683 // have their own buttons and whether there is at least one button present.
684 foreach ( $requests as $req ) {
685 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
686 if ( $this->hasOwnSubmitButton( $req ) ) {
687 $customSubmitButtonPresent = true;
688 } else {
689 return true;
690 }
691 }
692 }
693 return !$customSubmitButtonPresent;
694 }
695
701 protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
702 foreach ( $req->getFieldInfo() as $info ) {
703 if ( $info['type'] === 'button' ) {
704 return true;
705 }
706 }
707 return false;
708 }
709
715 protected function addTabIndex( &$formDescriptor ) {
716 $i = 1;
717 foreach ( $formDescriptor as &$definition ) {
718 $class = false;
719 if ( array_key_exists( 'class', $definition ) ) {
720 $class = $definition['class'];
721 } elseif ( array_key_exists( 'type', $definition ) ) {
722 $class = HTMLForm::$typeMappings[$definition['type']];
723 }
724 if ( $class !== HTMLInfoField::class ) {
725 $definition['tabindex'] = $i;
726 $i++;
727 }
728 }
729 }
730
736 protected function getToken() {
737 return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
738 . $this->getName() );
739 }
740
746 protected function getTokenName() {
747 return 'wpAuthToken';
748 }
749
759 protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
760 $formDescriptor = [];
761 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
762 $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
763 }
764
765 $requestSnapshot = serialize( $requests );
766 $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
767 $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
768 $formDescriptor, $action );
769 if ( $requestSnapshot !== serialize( $requests ) ) {
770 LoggerFactory::getInstance( 'authentication' )->warning(
771 'AuthChangeFormFields hook changed auth requests' );
772 }
773
774 // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
775 // subscribers (who only see one field at a time) to influence ordering.
776 self::sortFormDescriptorFields( $formDescriptor );
777
778 return $formDescriptor;
779 }
780
788 protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
789 $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
790 $descriptor = [
791 'type' => $type,
792 // Do not prefix input name with 'wp'. This is important for the redirect flow.
793 'name' => $fieldName,
794 ];
795
796 if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
797 $descriptor['default'] = $singleFieldInfo['label']->plain();
798 } elseif ( $type !== 'submit' ) {
799 $descriptor += array_filter( [
800 // help-message is omitted as it is usually not really useful for a web interface
801 'label-message' => self::getField( $singleFieldInfo, 'label' ),
802 ] );
803
804 if ( isset( $singleFieldInfo['options'] ) ) {
805 $descriptor['options'] = array_flip( array_map( static function ( $message ) {
807 return $message->parse();
808 }, $singleFieldInfo['options'] ) );
809 }
810
811 if ( isset( $singleFieldInfo['value'] ) ) {
812 $descriptor['default'] = $singleFieldInfo['value'];
813 }
814
815 if ( empty( $singleFieldInfo['optional'] ) ) {
816 $descriptor['required'] = true;
817 }
818 }
819
820 return $descriptor;
821 }
822
828 protected static function sortFormDescriptorFields( array &$formDescriptor ) {
829 $i = 0;
830 foreach ( $formDescriptor as &$field ) {
831 $field['__index'] = $i++;
832 }
833 unset( $field );
834 uasort( $formDescriptor, static function ( $first, $second ) {
835 return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
836 ?: $first['__index'] <=> $second['__index'];
837 } );
838 foreach ( $formDescriptor as &$field ) {
839 unset( $field['__index'] );
840 }
841 }
842
850 protected static function getField( array $array, $fieldName, $default = null ) {
851 if ( array_key_exists( $fieldName, $array ) ) {
852 return $array[$fieldName];
853 } else {
854 return $default;
855 }
856 }
857
865 protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
866 $map = [
867 'string' => 'text',
868 'password' => 'password',
869 'select' => 'select',
870 'checkbox' => 'check',
871 'multiselect' => 'multiselect',
872 'button' => 'submit',
873 'hidden' => 'hidden',
874 'null' => 'info',
875 ];
876 if ( !array_key_exists( $type, $map ) ) {
877 throw new InvalidArgumentException( 'invalid field type: ' . $type );
878 }
879 return $map[$type];
880 }
881
894 protected static function mergeDefaultFormDescriptor(
895 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
896 ) {
897 // keep the ordering from $defaultFormDescriptor where there is no explicit weight
898 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
899 // remove everything that is not in the fieldinfo, is not marked as a supplemental field
900 // to something in the fieldinfo, and is not an info field or a submit button
901 if (
902 !isset( $fieldInfo[$fieldName] )
903 && (
904 !isset( $defaultField['baseField'] )
905 || !isset( $fieldInfo[$defaultField['baseField']] )
906 )
907 && (
908 !isset( $defaultField['type'] )
909 || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
910 )
911 ) {
912 $defaultFormDescriptor[$fieldName] = null;
913 continue;
914 }
915
916 // default message labels should always take priority
917 $requestField = $formDescriptor[$fieldName] ?? [];
918 if (
919 isset( $defaultField['label'] )
920 || isset( $defaultField['label-message'] )
921 || isset( $defaultField['label-raw'] )
922 ) {
923 unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
924 }
925
926 $defaultFormDescriptor[$fieldName] += $requestField;
927 }
928
929 return array_filter( $defaultFormDescriptor + $formDescriptor );
930 }
931}
932
934class_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.
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:214
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
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:26
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:19
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.
getAuthenticationRequests( $action, ?UserIdentity $user=null)
Get the list of AuthenticationRequests from the AuthManager.
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.
Helper functions for the login form that need to be shared with other special pages (such as CentralA...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
A cryptographic random generator class used for generating secret keys.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Interface for objects representing user identity.