MediaWiki  master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1 <?php
2 
10 
20 abstract class AuthManagerSpecialPage extends SpecialPage {
24  protected static $allowedActions = [
25  AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
26  AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
27  AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
28  AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
29  ];
30 
32  protected static $messages = [];
33 
35  protected $authAction;
36 
38  protected $authRequests;
39 
41  protected $subPage;
42 
44  protected $isReturn;
45 
47  protected $savedRequest;
48 
61  public function onAuthChangeFormFields(
62  array $requests, array $fieldInfo, array &$formDescriptor, $action
63  ) {
64  }
65 
70  protected function getLoginSecurityLevel() {
71  return $this->getName();
72  }
73 
74  public function getRequest() {
75  return $this->savedRequest ?: $this->getContext()->getRequest();
76  }
77 
88  protected function setRequest( array $data, $wasPosted = null ) {
89  $request = $this->getContext()->getRequest();
90  $this->savedRequest = new DerivativeRequest(
91  $request,
92  $data + $request->getQueryValues(),
93  $wasPosted ?? $request->wasPosted()
94  );
95  }
96 
103  protected function beforeExecute( $subPage ) {
104  $this->getOutput()->disallowUserJs();
105 
106  return $this->handleReturnBeforeExecute( $subPage )
107  && $this->handleReauthBeforeExecute( $subPage );
108  }
109 
126  protected function handleReturnBeforeExecute( $subPage ) {
127  $authManager = $this->getAuthManager();
128  $key = 'AuthManagerSpecialPage:return:' . $this->getName();
129 
130  if ( $subPage === 'return' ) {
131  $this->loadAuth( $subPage );
132  $preservedParams = $this->getPreservedParams( false );
133 
134  // FIXME save POST values only from request
135  $authData = array_diff_key( $this->getRequest()->getValues(),
136  $preservedParams, [ 'title' => 1 ] );
137  $authManager->setAuthenticationSessionData( $key, $authData );
138 
139  $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
140  $this->getOutput()->redirect( $url );
141  return false;
142  }
143 
144  $authData = $authManager->getAuthenticationSessionData( $key );
145  if ( $authData ) {
146  $authManager->removeAuthenticationSessionData( $key );
147  $this->isReturn = true;
148  $this->setRequest( $authData, true );
149  }
150 
151  return true;
152  }
153 
164  protected function handleReauthBeforeExecute( $subPage ) {
165  $authManager = $this->getAuthManager();
166  $request = $this->getRequest();
167  $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
168 
169  $securityLevel = $this->getLoginSecurityLevel();
170  if ( $securityLevel ) {
171  $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
172  if ( $securityStatus === AuthManager::SEC_REAUTH ) {
173  $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
174 
175  if ( $request->wasPosted() ) {
176  // unique ID in case the same special page is open in multiple browser tabs
177  $uniqueId = MWCryptRand::generateHex( 6 );
178  $key .= ':' . $uniqueId;
179 
180  $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
181  $authData = array_diff_key( $request->getValues(),
182  $this->getPreservedParams( false ), [ 'title' => 1 ] );
183  $authManager->setAuthenticationSessionData( $key, $authData );
184  }
185 
186  $title = SpecialPage::getTitleFor( 'Userlogin' );
187  $url = $title->getFullURL( [
188  'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
189  'returntoquery' => wfArrayToCgi( $queryParams ),
190  'force' => $securityLevel,
191  ], false, PROTO_HTTPS );
192 
193  $this->getOutput()->redirect( $url );
194  return false;
195  } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
196  throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
197  }
198  }
199 
200  $uniqueId = $request->getVal( 'authUniqueId' );
201  if ( $uniqueId ) {
202  $key .= ':' . $uniqueId;
203  $authData = $authManager->getAuthenticationSessionData( $key );
204  if ( $authData ) {
205  $authManager->removeAuthenticationSessionData( $key );
206  $this->setRequest( $authData, true );
207  }
208  }
209 
210  return true;
211  }
212 
220  abstract protected function getDefaultAction( $subPage );
221 
228  protected function messageKey( $defaultKey ) {
229  return array_key_exists( $defaultKey, static::$messages )
230  ? static::$messages[$defaultKey] : $defaultKey;
231  }
232 
238  protected function getRequestBlacklist() {
239  return [];
240  }
241 
252  protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
253  // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
254  // calls. This is important for requests which have hidden information so any
255  // getAuthenticationRequests call would mean putting data into some cache.
256  if (
257  !$reset && $this->subPage === $subPage && $this->authAction
258  && ( !$authAction || $authAction === $this->authAction )
259  ) {
260  return;
261  }
262 
263  $request = $this->getRequest();
264  $this->subPage = $subPage;
265  $this->authAction = $authAction ?: $request->getText( 'authAction' );
266  if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
267  $this->authAction = $this->getDefaultAction( $subPage );
268  if ( $request->wasPosted() ) {
269  $continueAction = $this->getContinueAction( $this->authAction );
270  if ( in_array( $continueAction, static::$allowedActions, true ) ) {
271  $this->authAction = $continueAction;
272  }
273  }
274  }
275 
276  $allReqs = $this->getAuthManager()->getAuthenticationRequests(
277  $this->authAction, $this->getUser() );
278  $this->authRequests = array_filter( $allReqs, function ( $req ) {
279  return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
280  } );
281  }
282 
287  protected function isContinued() {
288  return in_array( $this->authAction, [
289  AuthManager::ACTION_LOGIN_CONTINUE,
290  AuthManager::ACTION_CREATE_CONTINUE,
291  AuthManager::ACTION_LINK_CONTINUE,
292  ], true );
293  }
294 
300  protected function getContinueAction( $action ) {
301  switch ( $action ) {
302  case AuthManager::ACTION_LOGIN:
303  $action = AuthManager::ACTION_LOGIN_CONTINUE;
304  break;
305  case AuthManager::ACTION_CREATE:
306  $action = AuthManager::ACTION_CREATE_CONTINUE;
307  break;
308  case AuthManager::ACTION_LINK:
309  $action = AuthManager::ACTION_LINK_CONTINUE;
310  break;
311  }
312  return $action;
313  }
314 
323  protected function isActionAllowed( $action ) {
324  $authManager = $this->getAuthManager();
325  if ( !in_array( $action, static::$allowedActions, true ) ) {
326  throw new InvalidArgumentException( 'invalid action: ' . $action );
327  }
328 
329  // calling getAuthenticationRequests can be expensive, avoid if possible
330  $requests = ( $action === $this->authAction ) ? $this->authRequests
331  : $authManager->getAuthenticationRequests( $action );
332  if ( !$requests ) {
333  // no provider supports this action in the current state
334  return false;
335  }
336 
337  switch ( $action ) {
338  case AuthManager::ACTION_LOGIN:
339  case AuthManager::ACTION_LOGIN_CONTINUE:
340  return $authManager->canAuthenticateNow();
341  case AuthManager::ACTION_CREATE:
342  case AuthManager::ACTION_CREATE_CONTINUE:
343  return $authManager->canCreateAccounts();
344  case AuthManager::ACTION_LINK:
345  case AuthManager::ACTION_LINK_CONTINUE:
346  return $authManager->canLinkAccounts();
347  case AuthManager::ACTION_CHANGE:
348  case AuthManager::ACTION_REMOVE:
349  case AuthManager::ACTION_UNLINK:
350  return true;
351  default:
352  // should never reach here but makes static code analyzers happy
353  throw new InvalidArgumentException( 'invalid action: ' . $action );
354  }
355  }
356 
363  protected function performAuthenticationStep( $action, array $requests ) {
364  if ( !in_array( $action, static::$allowedActions, true ) ) {
365  throw new InvalidArgumentException( 'invalid action: ' . $action );
366  }
367 
368  $authManager = $this->getAuthManager();
369  $returnToUrl = $this->getPageTitle( 'return' )
370  ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
371 
372  switch ( $action ) {
373  case AuthManager::ACTION_LOGIN:
374  return $authManager->beginAuthentication( $requests, $returnToUrl );
375  case AuthManager::ACTION_LOGIN_CONTINUE:
376  return $authManager->continueAuthentication( $requests );
377  case AuthManager::ACTION_CREATE:
378  return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
379  $returnToUrl );
380  case AuthManager::ACTION_CREATE_CONTINUE:
381  return $authManager->continueAccountCreation( $requests );
382  case AuthManager::ACTION_LINK:
383  return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
384  case AuthManager::ACTION_LINK_CONTINUE:
385  return $authManager->continueAccountLink( $requests );
386  case AuthManager::ACTION_CHANGE:
387  case AuthManager::ACTION_REMOVE:
388  case AuthManager::ACTION_UNLINK:
389  if ( count( $requests ) > 1 ) {
390  throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
391  } elseif ( !$requests ) {
392  throw new InvalidArgumentException( 'no auth request' );
393  }
394  $req = reset( $requests );
395  $status = $authManager->allowsAuthenticationDataChange( $req );
396  $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
397  if ( !$status->isGood() ) {
398  return AuthenticationResponse::newFail( $status->getMessage() );
399  }
400  $authManager->changeAuthenticationData( $req );
401  return AuthenticationResponse::newPass();
402  default:
403  // should never reach here but makes static code analyzers happy
404  throw new InvalidArgumentException( 'invalid action: ' . $action );
405  }
406  }
407 
418  protected function trySubmit() {
419  $status = false;
420 
421  $form = $this->getAuthForm( $this->authRequests, $this->authAction );
422  $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
423 
424  if ( $this->getRequest()->wasPosted() ) {
425  // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
426  $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
427  $sessionToken = $this->getToken();
428  if ( $sessionToken->wasNew() ) {
429  return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
430  } elseif ( !$requestTokenValue ) {
431  return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
432  } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
433  return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
434  }
435 
436  $form->prepareForm();
437  $status = $form->trySubmit();
438 
439  // HTMLForm submit return values are a mess; let's ensure it is false or a Status
440  // FIXME this probably should be in HTMLForm
441  if ( $status === true ) {
442  // not supposed to happen since our submit handler should always return a Status
443  throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
444  } elseif ( $status === false ) {
445  // form was not submitted; nothing to do
446  } elseif ( $status instanceof Status ) {
447  // already handled by the form; nothing to do
448  } elseif ( $status instanceof StatusValue ) {
449  // in theory not an allowed return type but nothing stops the submit handler from
450  // accidentally returning it so best check and fix
451  $status = Status::wrap( $status );
452  } elseif ( is_string( $status ) ) {
453  $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
454  } elseif ( is_array( $status ) ) {
455  if ( is_string( reset( $status ) ) ) {
456  // @phan-suppress-next-line PhanParamTooFewUnpack
457  $status = Status::newFatal( ...$status );
458  } elseif ( is_array( reset( $status ) ) ) {
459  $ret = Status::newGood();
460  foreach ( $status as $message ) {
461  // @phan-suppress-next-line PhanParamTooFewUnpack
462  $ret->fatal( ...$message );
463  }
464  $status = $ret;
465  } else {
466  throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
467  . 'first element of array is ' . gettype( reset( $status ) ) );
468  }
469  } else {
470  // not supposed to happen but HTMLForm does not actually verify the return type
471  // from the submit callback; better safe then sorry
472  throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
473  . gettype( $status ) );
474  }
475 
476  if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
477  // This is awkward. There was a form validation error, which means the data was not
478  // passed to AuthManager. Normally we would display the form with an error message,
479  // but for the data we received via the redirect flow that would not be helpful at all.
480  // Let's just submit the data to AuthManager directly instead.
481  LoggerFactory::getInstance( 'authentication' )
482  ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
483  'status' => $status->getWikiText( false, false, 'en' ) ] );
484  $status = $this->handleFormSubmit( $form->mFieldData );
485  }
486  }
487 
488  $changeActions = [
489  AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
490  ];
491  if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
492  $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
493  }
494 
495  return $status;
496  }
497 
504  public function handleFormSubmit( $data ) {
505  $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
506  $response = $this->performAuthenticationStep( $this->authAction, $requests );
507 
508  // we can't handle FAIL or similar as failure here since it might require changing the form
509  return Status::newGood( $response );
510  }
511 
520  protected function getPreservedParams( $withToken = false ) {
521  $params = [];
522  if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
523  $params['authAction'] = $this->getContinueAction( $this->authAction );
524  }
525  if ( $withToken ) {
526  $params[$this->getTokenName()] = $this->getToken()->toString();
527  }
528  return $params;
529  }
530 
538  protected function getAuthFormDescriptor( $requests, $action ) {
539  $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
540  $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
541 
542  $this->addTabIndex( $formDescriptor );
543 
544  return $formDescriptor;
545  }
546 
553  protected function getAuthForm( array $requests, $action ) {
554  $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
555  $context = $this->getContext();
556  if ( $context->getRequest() !== $this->getRequest() ) {
557  // We have overridden the request, need to make sure the form uses that too.
558  $context = new DerivativeContext( $this->getContext() );
559  $context->setRequest( $this->getRequest() );
560  }
561  $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
562  $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
563  $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
564  $form->addHiddenField( 'authAction', $this->authAction );
565  $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
566 
567  return $form;
568  }
569 
574  protected function displayForm( $status ) {
575  if ( $status instanceof StatusValue ) {
576  $status = Status::wrap( $status );
577  }
578  $form = $this->getAuthForm( $this->authRequests, $this->authAction );
579  $form->prepareForm()->displayForm( $status );
580  }
581 
593  protected function needsSubmitButton( array $requests ) {
594  $customSubmitButtonPresent = false;
595 
596  // Secondary and preauth providers always need their data; they will not care what button
597  // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
598  // that's the point in being optional. Se we need to check whether all primary providers
599  // have their own buttons and whether there is at least one button present.
600  foreach ( $requests as $req ) {
601  if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
602  if ( $this->hasOwnSubmitButton( $req ) ) {
603  $customSubmitButtonPresent = true;
604  } else {
605  return true;
606  }
607  }
608  }
609  return !$customSubmitButtonPresent;
610  }
611 
617  protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
618  foreach ( $req->getFieldInfo() as $info ) {
619  if ( $info['type'] === 'button' ) {
620  return true;
621  }
622  }
623  return false;
624  }
625 
631  protected function addTabIndex( &$formDescriptor ) {
632  $i = 1;
633  foreach ( $formDescriptor as &$definition ) {
634  $class = false;
635  if ( array_key_exists( 'class', $definition ) ) {
636  $class = $definition['class'];
637  } elseif ( array_key_exists( 'type', $definition ) ) {
638  $class = HTMLForm::$typeMappings[$definition['type']];
639  }
640  if ( $class !== HTMLInfoField::class ) {
641  $definition['tabindex'] = $i;
642  $i++;
643  }
644  }
645  }
646 
652  protected function getToken() {
653  return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
654  . $this->getName() );
655  }
656 
662  protected function getTokenName() {
663  return 'wpAuthToken';
664  }
665 
675  protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
676  $formDescriptor = [];
677  foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
678  $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
679  }
680 
681  $requestSnapshot = serialize( $requests );
682  $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
683  $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
684  $formDescriptor, $action );
685  if ( $requestSnapshot !== serialize( $requests ) ) {
686  LoggerFactory::getInstance( 'authentication' )->warning(
687  'AuthChangeFormFields hook changed auth requests' );
688  }
689 
690  // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
691  // subscribers (who only see one field at a time) to influence ordering.
692  self::sortFormDescriptorFields( $formDescriptor );
693 
694  return $formDescriptor;
695  }
696 
704  protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
705  $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
706  $descriptor = [
707  'type' => $type,
708  // Do not prefix input name with 'wp'. This is important for the redirect flow.
709  'name' => $fieldName,
710  ];
711 
712  if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
713  $descriptor['default'] = $singleFieldInfo['label']->plain();
714  } elseif ( $type !== 'submit' ) {
715  $descriptor += array_filter( [
716  // help-message is omitted as it is usually not really useful for a web interface
717  'label-message' => self::getField( $singleFieldInfo, 'label' ),
718  ] );
719 
720  if ( isset( $singleFieldInfo['options'] ) ) {
721  $descriptor['options'] = array_flip( array_map( static function ( $message ) {
723  return $message->parse();
724  }, $singleFieldInfo['options'] ) );
725  }
726 
727  if ( isset( $singleFieldInfo['value'] ) ) {
728  $descriptor['default'] = $singleFieldInfo['value'];
729  }
730 
731  if ( empty( $singleFieldInfo['optional'] ) ) {
732  $descriptor['required'] = true;
733  }
734  }
735 
736  return $descriptor;
737  }
738 
745  protected static function sortFormDescriptorFields( array &$formDescriptor ) {
746  $i = 0;
747  foreach ( $formDescriptor as &$field ) {
748  $field['__index'] = $i++;
749  }
750  uasort( $formDescriptor, function ( $first, $second ) {
751  return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
752  ?: $first['__index'] <=> $second['__index'];
753  } );
754  foreach ( $formDescriptor as &$field ) {
755  unset( $field['__index'] );
756  }
757  }
758 
766  protected static function getField( array $array, $fieldName, $default = null ) {
767  if ( array_key_exists( $fieldName, $array ) ) {
768  return $array[$fieldName];
769  } else {
770  return $default;
771  }
772  }
773 
780  protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
781  $map = [
782  'string' => 'text',
783  'password' => 'password',
784  'select' => 'select',
785  'checkbox' => 'check',
786  'multiselect' => 'multiselect',
787  'button' => 'submit',
788  'hidden' => 'hidden',
789  'null' => 'info',
790  ];
791  if ( !array_key_exists( $type, $map ) ) {
792  throw new \LogicException( 'invalid field type: ' . $type );
793  }
794  return $map[$type];
795  }
796 
809  protected static function mergeDefaultFormDescriptor(
810  array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
811  ) {
812  // keep the ordering from $defaultFormDescriptor where there is no explicit weight
813  foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
814  // remove everything that is not in the fieldinfo, is not marked as a supplemental field
815  // to something in the fieldinfo, and is not an info field or a submit button
816  if (
817  !isset( $fieldInfo[$fieldName] )
818  && (
819  !isset( $defaultField['baseField'] )
820  || !isset( $fieldInfo[$defaultField['baseField']] )
821  )
822  && (
823  !isset( $defaultField['type'] )
824  || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
825  )
826  ) {
827  $defaultFormDescriptor[$fieldName] = null;
828  continue;
829  }
830 
831  // default message labels should always take priority
832  $requestField = $formDescriptor[$fieldName] ?? [];
833  if (
834  isset( $defaultField['label'] )
835  || isset( $defaultField['label-message'] )
836  || isset( $defaultField['label-raw'] )
837  ) {
838  unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
839  }
840 
841  $defaultFormDescriptor[$fieldName] += $requestField;
842  }
843 
844  return array_filter( $defaultFormDescriptor + $formDescriptor );
845  }
846 }
const PROTO_HTTPS
Definition: Defines.php:194
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
A special page subclass for authentication-related special pages.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
handleReturnBeforeExecute( $subPage)
Handle redirection from the /return subpage.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
WebRequest null $savedRequest
If set, will be used instead of the real request.
string $authAction
one of the AuthManager::ACTION_* constants.
performAuthenticationStep( $action, array $requests)
getAuthForm(array $requests, $action)
displayForm( $status)
Display the form.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
bool $isReturn
True if the current request is a result of returning from a redirect flow.
static mapFieldInfoTypeToFormDescriptorType( $type)
Maps AuthenticationRequest::getFieldInfo() types to HTMLForm types.
fieldInfoToFormDescriptor(array $requests, array $fieldInfo, $action)
Turns a field info array into a form descriptor.
getDefaultAction( $subPage)
Get the default action for this special page, if none is given via URL/POST data.
handleFormSubmit( $data)
Submit handler callback for HTMLForm.
static array $messages
Customized messages.
AuthenticationRequest[] $authRequests
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
string $subPage
Subpage of the special page.
isContinued()
Returns true if this is not the first step of the authentication.
static mergeDefaultFormDescriptor(array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor)
Apply defaults to a form descriptor, without creating non-existent fields.
hasOwnSubmitButton(AuthenticationRequest $req)
Checks whether the given AuthenticationRequest has its own submit button.
getRequestBlacklist()
Allows blacklisting certain request types.
static mapSingleFieldInfo( $singleFieldInfo, $fieldName)
Maps an authentication field configuration for a single field (as returned by AuthenticationRequest::...
static getField(array $array, $fieldName, $default=null)
Get an array value, or a default if it does not exist.
getRequest()
Get the WebRequest being used for this instance.
messageKey( $defaultKey)
Return custom message key.
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
trySubmit()
Attempts to do an authentication step with the submitted data.
getToken()
Returns the CSRF token.
static sortFormDescriptorFields(array &$formDescriptor)
Sort the fields of a form descriptor by their 'weight' property.
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
getAuthFormDescriptor( $requests, $action)
Generates a HTMLForm descriptor array from a set of authentication requests.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form.
getPreservedParams( $withToken=false)
Returns URL query parameters which can be used to reload the page (or leave and return) while preserv...
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
static string[] $allowedActions
The list of actions this special page deals with.
An IContextSource implementation which will inherit context from another source but allow individual ...
An error page which can definitely be safely rendered using the OutputPage.
static string[] $typeMappings
A mapping of 'type' inputs onto standard HTMLFormField subclasses.
Definition: HTMLForm.php:159
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:354
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
Definition: MWCryptRand.php:36
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.
Variant of the Message class.
Definition: RawMessage.php:40
PSR-3 logger instance factory.
Similar to MediaWiki\Request\FauxRequest, but only fakes URL parameters and method (POST or GET) and ...
Value object representing a CSRF token.
Definition: Token.php:32
Parent class for all special pages.
Definition: SpecialPage.php:45
getName()
Get the name of this Special Page.
getOutput()
Get the OutputPage being used for this instance.
getUser()
Shortcut to get the User executing this instance.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
getContext()
Gets the context this SpecialPage is executed in.
getAuthority()
Shortcut to get the Authority executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:46
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:46
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:64