MediaWiki  master
AuthManagerSpecialPage.php
Go to the documentation of this file.
1 <?php
2 
8 
18 abstract class AuthManagerSpecialPage extends SpecialPage {
22  protected static $allowedActions = [
23  AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
24  AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
25  AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
26  AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
27  ];
28 
30  protected static $messages = [];
31 
33  protected $authAction;
34 
36  protected $authRequests;
37 
39  protected $subPage;
40 
42  protected $isReturn;
43 
45  protected $savedRequest;
46 
59  public function onAuthChangeFormFields(
60  array $requests, array $fieldInfo, array &$formDescriptor, $action
61  ) {
62  }
63 
68  protected function getLoginSecurityLevel() {
69  return $this->getName();
70  }
71 
72  public function getRequest() {
73  return $this->savedRequest ?: $this->getContext()->getRequest();
74  }
75 
86  protected function setRequest( array $data, $wasPosted = null ) {
87  $request = $this->getContext()->getRequest();
88  if ( $wasPosted === null ) {
89  $wasPosted = $request->wasPosted();
90  }
91  $this->savedRequest = new DerivativeRequest( $request, $data + $request->getQueryValues(),
92  $wasPosted );
93  }
94 
101  protected function beforeExecute( $subPage ) {
102  $this->getOutput()->disallowUserJs();
103 
104  return $this->handleReturnBeforeExecute( $subPage )
105  && $this->handleReauthBeforeExecute( $subPage );
106  }
107 
124  protected function handleReturnBeforeExecute( $subPage ) {
125  $authManager = $this->getAuthManager();
126  $key = 'AuthManagerSpecialPage:return:' . $this->getName();
127 
128  if ( $subPage === 'return' ) {
129  $this->loadAuth( $subPage );
130  $preservedParams = $this->getPreservedParams( false );
131 
132  // FIXME save POST values only from request
133  $authData = array_diff_key( $this->getRequest()->getValues(),
134  $preservedParams, [ 'title' => 1 ] );
135  $authManager->setAuthenticationSessionData( $key, $authData );
136 
137  $url = $this->getPageTitle()->getFullURL( $preservedParams, false, PROTO_HTTPS );
138  $this->getOutput()->redirect( $url );
139  return false;
140  }
141 
142  $authData = $authManager->getAuthenticationSessionData( $key );
143  if ( $authData ) {
145  $this->isReturn = true;
146  $this->setRequest( $authData, true );
147  }
148 
149  return true;
150  }
151 
162  protected function handleReauthBeforeExecute( $subPage ) {
163  $authManager = $this->getAuthManager();
164  $request = $this->getRequest();
165  $key = 'AuthManagerSpecialPage:reauth:' . $this->getName();
166 
167  $securityLevel = $this->getLoginSecurityLevel();
168  if ( $securityLevel ) {
169  $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
170  if ( $securityStatus === AuthManager::SEC_REAUTH ) {
171  $queryParams = array_diff_key( $request->getQueryValues(), [ 'title' => true ] );
172 
173  if ( $request->wasPosted() ) {
174  // unique ID in case the same special page is open in multiple browser tabs
175  $uniqueId = MWCryptRand::generateHex( 6 );
176  $key .= ':' . $uniqueId;
177 
178  $queryParams = [ 'authUniqueId' => $uniqueId ] + $queryParams;
179  $authData = array_diff_key( $request->getValues(),
180  $this->getPreservedParams( false ), [ 'title' => 1 ] );
181  $authManager->setAuthenticationSessionData( $key, $authData );
182  }
183 
184  $title = SpecialPage::getTitleFor( 'Userlogin' );
185  $url = $title->getFullURL( [
186  'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
187  'returntoquery' => wfArrayToCgi( $queryParams ),
188  'force' => $securityLevel,
189  ], false, PROTO_HTTPS );
190 
191  $this->getOutput()->redirect( $url );
192  return false;
193  } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
194  throw new ErrorPageError( 'cannotauth-not-allowed-title', 'cannotauth-not-allowed' );
195  }
196  }
197 
198  $uniqueId = $request->getVal( 'authUniqueId' );
199  if ( $uniqueId ) {
200  $key .= ':' . $uniqueId;
201  $authData = $authManager->getAuthenticationSessionData( $key );
202  if ( $authData ) {
204  $this->setRequest( $authData, true );
205  }
206  }
207 
208  return true;
209  }
210 
218  abstract protected function getDefaultAction( $subPage );
219 
226  protected function messageKey( $defaultKey ) {
227  return array_key_exists( $defaultKey, static::$messages )
228  ? static::$messages[$defaultKey] : $defaultKey;
229  }
230 
236  protected function getRequestBlacklist() {
237  return [];
238  }
239 
250  protected function loadAuth( $subPage, $authAction = null, $reset = false ) {
251  // Do not load if already loaded, to cut down on the number of getAuthenticationRequests
252  // calls. This is important for requests which have hidden information so any
253  // getAuthenticationRequests call would mean putting data into some cache.
254  if (
255  !$reset && $this->subPage === $subPage && $this->authAction
256  && ( !$authAction || $authAction === $this->authAction )
257  ) {
258  return;
259  }
260 
261  $request = $this->getRequest();
262  $this->subPage = $subPage;
263  $this->authAction = $authAction ?: $request->getText( 'authAction' );
264  if ( !in_array( $this->authAction, static::$allowedActions, true ) ) {
265  $this->authAction = $this->getDefaultAction( $subPage );
266  if ( $request->wasPosted() ) {
267  $continueAction = $this->getContinueAction( $this->authAction );
268  if ( in_array( $continueAction, static::$allowedActions, true ) ) {
269  $this->authAction = $continueAction;
270  }
271  }
272  }
273 
274  $allReqs = $this->getAuthManager()->getAuthenticationRequests(
275  $this->authAction, $this->getUser() );
276  $this->authRequests = array_filter( $allReqs, function ( $req ) {
277  return !in_array( get_class( $req ), $this->getRequestBlacklist(), true );
278  } );
279  }
280 
285  protected function isContinued() {
286  return in_array( $this->authAction, [
287  AuthManager::ACTION_LOGIN_CONTINUE,
288  AuthManager::ACTION_CREATE_CONTINUE,
289  AuthManager::ACTION_LINK_CONTINUE,
290  ], true );
291  }
292 
298  protected function getContinueAction( $action ) {
299  switch ( $action ) {
300  case AuthManager::ACTION_LOGIN:
301  $action = AuthManager::ACTION_LOGIN_CONTINUE;
302  break;
303  case AuthManager::ACTION_CREATE:
304  $action = AuthManager::ACTION_CREATE_CONTINUE;
305  break;
306  case AuthManager::ACTION_LINK:
307  $action = AuthManager::ACTION_LINK_CONTINUE;
308  break;
309  }
310  return $action;
311  }
312 
321  protected function isActionAllowed( $action ) {
322  $authManager = $this->getAuthManager();
323  if ( !in_array( $action, static::$allowedActions, true ) ) {
324  throw new InvalidArgumentException( 'invalid action: ' . $action );
325  }
326 
327  // calling getAuthenticationRequests can be expensive, avoid if possible
328  $requests = ( $action === $this->authAction ) ? $this->authRequests
330  if ( !$requests ) {
331  // no provider supports this action in the current state
332  return false;
333  }
334 
335  switch ( $action ) {
336  case AuthManager::ACTION_LOGIN:
337  case AuthManager::ACTION_LOGIN_CONTINUE:
339  case AuthManager::ACTION_CREATE:
340  case AuthManager::ACTION_CREATE_CONTINUE:
342  case AuthManager::ACTION_LINK:
343  case AuthManager::ACTION_LINK_CONTINUE:
344  return $authManager->canLinkAccounts();
345  case AuthManager::ACTION_CHANGE:
346  case AuthManager::ACTION_REMOVE:
347  case AuthManager::ACTION_UNLINK:
348  return true;
349  default:
350  // should never reach here but makes static code analyzers happy
351  throw new InvalidArgumentException( 'invalid action: ' . $action );
352  }
353  }
354 
361  protected function performAuthenticationStep( $action, array $requests ) {
362  if ( !in_array( $action, static::$allowedActions, true ) ) {
363  throw new InvalidArgumentException( 'invalid action: ' . $action );
364  }
365 
366  $authManager = $this->getAuthManager();
367  $returnToUrl = $this->getPageTitle( 'return' )
368  ->getFullURL( $this->getPreservedParams( true ), false, PROTO_HTTPS );
369 
370  switch ( $action ) {
371  case AuthManager::ACTION_LOGIN:
372  return $authManager->beginAuthentication( $requests, $returnToUrl );
373  case AuthManager::ACTION_LOGIN_CONTINUE:
374  return $authManager->continueAuthentication( $requests );
375  case AuthManager::ACTION_CREATE:
376  return $authManager->beginAccountCreation( $this->getAuthority(), $requests,
377  $returnToUrl );
378  case AuthManager::ACTION_CREATE_CONTINUE:
379  return $authManager->continueAccountCreation( $requests );
380  case AuthManager::ACTION_LINK:
381  return $authManager->beginAccountLink( $this->getUser(), $requests, $returnToUrl );
382  case AuthManager::ACTION_LINK_CONTINUE:
383  return $authManager->continueAccountLink( $requests );
384  case AuthManager::ACTION_CHANGE:
385  case AuthManager::ACTION_REMOVE:
386  case AuthManager::ACTION_UNLINK:
387  if ( count( $requests ) > 1 ) {
388  throw new InvalidArgumentException( 'only one auth request can be changed at a time' );
389  } elseif ( !$requests ) {
390  throw new InvalidArgumentException( 'no auth request' );
391  }
392  $req = reset( $requests );
393  $status = $authManager->allowsAuthenticationDataChange( $req );
394  $this->getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
395  if ( !$status->isGood() ) {
396  return AuthenticationResponse::newFail( $status->getMessage() );
397  }
399  return AuthenticationResponse::newPass();
400  default:
401  // should never reach here but makes static code analyzers happy
402  throw new InvalidArgumentException( 'invalid action: ' . $action );
403  }
404  }
405 
416  protected function trySubmit() {
417  $status = false;
418 
419  $form = $this->getAuthForm( $this->authRequests, $this->authAction );
420  $form->setSubmitCallback( [ $this, 'handleFormSubmit' ] );
421 
422  if ( $this->getRequest()->wasPosted() ) {
423  // handle tokens manually; $form->tryAuthorizedSubmit only works for logged-in users
424  $requestTokenValue = $this->getRequest()->getVal( $this->getTokenName() );
425  $sessionToken = $this->getToken();
426  if ( $sessionToken->wasNew() ) {
427  return Status::newFatal( $this->messageKey( 'authform-newtoken' ) );
428  } elseif ( !$requestTokenValue ) {
429  return Status::newFatal( $this->messageKey( 'authform-notoken' ) );
430  } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
431  return Status::newFatal( $this->messageKey( 'authform-wrongtoken' ) );
432  }
433 
434  $form->prepareForm();
435  $status = $form->trySubmit();
436 
437  // HTMLForm submit return values are a mess; let's ensure it is false or a Status
438  // FIXME this probably should be in HTMLForm
439  if ( $status === true ) {
440  // not supposed to happen since our submit handler should always return a Status
441  throw new UnexpectedValueException( 'HTMLForm::trySubmit() returned true' );
442  } elseif ( $status === false ) {
443  // form was not submitted; nothing to do
444  } elseif ( $status instanceof Status ) {
445  // already handled by the form; nothing to do
446  } elseif ( $status instanceof StatusValue ) {
447  // in theory not an allowed return type but nothing stops the submit handler from
448  // accidentally returning it so best check and fix
449  $status = Status::wrap( $status );
450  } elseif ( is_string( $status ) ) {
451  $status = Status::newFatal( new RawMessage( '$1', [ $status ] ) );
452  } elseif ( is_array( $status ) ) {
453  if ( is_string( reset( $status ) ) ) {
454  $status = Status::newFatal( ...$status );
455  } elseif ( is_array( reset( $status ) ) ) {
456  $ret = Status::newGood();
457  foreach ( $status as $message ) {
458  $ret->fatal( ...$message );
459  }
460  $status = $ret;
461  } else {
462  throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return value: '
463  . 'first element of array is ' . gettype( reset( $status ) ) );
464  }
465  } else {
466  // not supposed to happen but HTMLForm does not actually verify the return type
467  // from the submit callback; better safe then sorry
468  throw new UnexpectedValueException( 'invalid HTMLForm::trySubmit() return type: '
469  . gettype( $status ) );
470  }
471 
472  if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
473  // This is awkward. There was a form validation error, which means the data was not
474  // passed to AuthManager. Normally we would display the form with an error message,
475  // but for the data we received via the redirect flow that would not be helpful at all.
476  // Let's just submit the data to AuthManager directly instead.
477  LoggerFactory::getInstance( 'authentication' )
478  ->warning( 'Validation error on return', [ 'data' => $form->mFieldData,
479  'status' => $status->getWikiText( false, false, 'en' ) ] );
480  $status = $this->handleFormSubmit( $form->mFieldData );
481  }
482  }
483 
484  $changeActions = [
485  AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
486  ];
487  if ( in_array( $this->authAction, $changeActions, true ) && $status && !$status->isOK() ) {
488  $this->getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
489  }
490 
491  return $status;
492  }
493 
500  public function handleFormSubmit( $data ) {
501  $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
502  $response = $this->performAuthenticationStep( $this->authAction, $requests );
503 
504  // we can't handle FAIL or similar as failure here since it might require changing the form
505  return Status::newGood( $response );
506  }
507 
516  protected function getPreservedParams( $withToken = false ) {
517  $params = [];
518  if ( $this->authAction !== $this->getDefaultAction( $this->subPage ) ) {
519  $params['authAction'] = $this->getContinueAction( $this->authAction );
520  }
521  if ( $withToken ) {
522  $params[$this->getTokenName()] = $this->getToken()->toString();
523  }
524  return $params;
525  }
526 
534  protected function getAuthFormDescriptor( $requests, $action ) {
535  $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
536  $formDescriptor = $this->fieldInfoToFormDescriptor( $requests, $fieldInfo, $action );
537 
538  $this->addTabIndex( $formDescriptor );
539 
540  return $formDescriptor;
541  }
542 
549  protected function getAuthForm( array $requests, $action ) {
550  $formDescriptor = $this->getAuthFormDescriptor( $requests, $action );
551  $context = $this->getContext();
552  if ( $context->getRequest() !== $this->getRequest() ) {
553  // We have overridden the request, need to make sure the form uses that too.
554  $context = new DerivativeContext( $this->getContext() );
555  $context->setRequest( $this->getRequest() );
556  }
557  $form = HTMLForm::factory( 'ooui', $formDescriptor, $context );
558  $form->setAction( $this->getFullTitle()->getFullURL( $this->getPreservedParams() ) );
559  $form->addHiddenField( $this->getTokenName(), $this->getToken()->toString() );
560  $form->addHiddenField( 'authAction', $this->authAction );
561  $form->suppressDefaultSubmit( !$this->needsSubmitButton( $requests ) );
562 
563  return $form;
564  }
565 
570  protected function displayForm( $status ) {
571  if ( $status instanceof StatusValue ) {
572  $status = Status::wrap( $status );
573  }
574  $form = $this->getAuthForm( $this->authRequests, $this->authAction );
575  $form->prepareForm()->displayForm( $status );
576  }
577 
589  protected function needsSubmitButton( array $requests ) {
590  $customSubmitButtonPresent = false;
591 
592  // Secondary and preauth providers always need their data; they will not care what button
593  // is used, so they can be ignored. So can OPTIONAL buttons createdby primary providers;
594  // that's the point in being optional. Se we need to check whether all primary providers
595  // have their own buttons and whether there is at least one button present.
596  foreach ( $requests as $req ) {
597  if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
598  if ( $this->hasOwnSubmitButton( $req ) ) {
599  $customSubmitButtonPresent = true;
600  } else {
601  return true;
602  }
603  }
604  }
605  return !$customSubmitButtonPresent;
606  }
607 
613  protected function hasOwnSubmitButton( AuthenticationRequest $req ) {
614  foreach ( $req->getFieldInfo() as $field => $info ) {
615  if ( $info['type'] === 'button' ) {
616  return true;
617  }
618  }
619  return false;
620  }
621 
627  protected function addTabIndex( &$formDescriptor ) {
628  $i = 1;
629  foreach ( $formDescriptor as $field => &$definition ) {
630  $class = false;
631  if ( array_key_exists( 'class', $definition ) ) {
632  $class = $definition['class'];
633  } elseif ( array_key_exists( 'type', $definition ) ) {
634  $class = HTMLForm::$typeMappings[$definition['type']];
635  }
636  if ( $class !== HTMLInfoField::class ) {
637  $definition['tabindex'] = $i;
638  $i++;
639  }
640  }
641  }
642 
648  protected function getToken() {
649  return $this->getRequest()->getSession()->getToken( 'AuthManagerSpecialPage:'
650  . $this->getName() );
651  }
652 
658  protected function getTokenName() {
659  return 'wpAuthToken';
660  }
661 
671  protected function fieldInfoToFormDescriptor( array $requests, array $fieldInfo, $action ) {
672  $formDescriptor = [];
673  foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
674  $formDescriptor[$fieldName] = self::mapSingleFieldInfo( $singleFieldInfo, $fieldName );
675  }
676 
677  $requestSnapshot = serialize( $requests );
678  $this->onAuthChangeFormFields( $requests, $fieldInfo, $formDescriptor, $action );
679  $this->getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
680  $formDescriptor, $action );
681  if ( $requestSnapshot !== serialize( $requests ) ) {
682  LoggerFactory::getInstance( 'authentication' )->warning(
683  'AuthChangeFormFields hook changed auth requests' );
684  }
685 
686  // Process the special 'weight' property, which is a way for AuthChangeFormFields hook
687  // subscribers (who only see one field at a time) to influence ordering.
688  self::sortFormDescriptorFields( $formDescriptor );
689 
690  return $formDescriptor;
691  }
692 
700  protected static function mapSingleFieldInfo( $singleFieldInfo, $fieldName ) {
701  $type = self::mapFieldInfoTypeToFormDescriptorType( $singleFieldInfo['type'] );
702  $descriptor = [
703  'type' => $type,
704  // Do not prefix input name with 'wp'. This is important for the redirect flow.
705  'name' => $fieldName,
706  ];
707 
708  if ( $type === 'submit' && isset( $singleFieldInfo['label'] ) ) {
709  $descriptor['default'] = $singleFieldInfo['label']->plain();
710  } elseif ( $type !== 'submit' ) {
711  $descriptor += array_filter( [
712  // help-message is omitted as it is usually not really useful for a web interface
713  'label-message' => self::getField( $singleFieldInfo, 'label' ),
714  ] );
715 
716  if ( isset( $singleFieldInfo['options'] ) ) {
717  $descriptor['options'] = array_flip( array_map( static function ( $message ) {
719  return $message->parse();
720  }, $singleFieldInfo['options'] ) );
721  }
722 
723  if ( isset( $singleFieldInfo['value'] ) ) {
724  $descriptor['default'] = $singleFieldInfo['value'];
725  }
726 
727  if ( empty( $singleFieldInfo['optional'] ) ) {
728  $descriptor['required'] = true;
729  }
730  }
731 
732  return $descriptor;
733  }
734 
741  protected static function sortFormDescriptorFields( array &$formDescriptor ) {
742  $i = 0;
743  foreach ( $formDescriptor as &$field ) {
744  $field['__index'] = $i++;
745  }
746  uasort( $formDescriptor, function ( $first, $second ) {
747  return self::getField( $first, 'weight', 0 ) <=> self::getField( $second, 'weight', 0 )
748  ?: $first['__index'] <=> $second['__index'];
749  } );
750  foreach ( $formDescriptor as &$field ) {
751  unset( $field['__index'] );
752  }
753  }
754 
762  protected static function getField( array $array, $fieldName, $default = null ) {
763  if ( array_key_exists( $fieldName, $array ) ) {
764  return $array[$fieldName];
765  } else {
766  return $default;
767  }
768  }
769 
776  protected static function mapFieldInfoTypeToFormDescriptorType( $type ) {
777  $map = [
778  'string' => 'text',
779  'password' => 'password',
780  'select' => 'select',
781  'checkbox' => 'check',
782  'multiselect' => 'multiselect',
783  'button' => 'submit',
784  'hidden' => 'hidden',
785  'null' => 'info',
786  ];
787  if ( !array_key_exists( $type, $map ) ) {
788  throw new \LogicException( 'invalid field type: ' . $type );
789  }
790  return $map[$type];
791  }
792 
805  protected static function mergeDefaultFormDescriptor(
806  array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
807  ) {
808  // keep the ordering from $defaultFormDescriptor where there is no explicit weight
809  foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
810  // remove everything that is not in the fieldinfo, is not marked as a supplemental field
811  // to something in the fieldinfo, and is not an info field or a submit button
812  if (
813  !isset( $fieldInfo[$fieldName] )
814  && (
815  !isset( $defaultField['baseField'] )
816  || !isset( $fieldInfo[$defaultField['baseField']] )
817  )
818  && (
819  !isset( $defaultField['type'] )
820  || !in_array( $defaultField['type'], [ 'submit', 'info' ], true )
821  )
822  ) {
823  $defaultFormDescriptor[$fieldName] = null;
824  continue;
825  }
826 
827  // default message labels should always take priority
828  $requestField = $formDescriptor[$fieldName] ?? [];
829  if (
830  isset( $defaultField['label'] )
831  || isset( $defaultField['label-message'] )
832  || isset( $defaultField['label-raw'] )
833  ) {
834  unset( $requestField['label'], $requestField['label-message'], $defaultField['label-raw'] );
835  }
836 
837  $defaultFormDescriptor[$fieldName] += $requestField;
838  }
839 
840  return array_filter( $defaultFormDescriptor + $formDescriptor );
841  }
842 }
serialize()
const PROTO_HTTPS
Definition: Defines.php:193
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 ...
Similar to FauxRequest, but only fakes URL parameters and method (POST or GET) and use the base reque...
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:154
static factory( $displayFormat, $descriptor, IContextSource $context, $messagePrefix='')
Construct a HTMLForm object for given display type.
Definition: HTMLForm.php:344
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.
canLinkAccounts()
Determine whether accounts can be linked.
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
continueAccountLink(array $reqs)
Continue an account linking flow.
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
beginAccountCreation(Authority $creator, array $reqs, $returnToUrl)
Start an account creation flow.
canCreateAccounts()
Determine whether accounts can be created.
changeAuthenticationData(AuthenticationRequest $req, $isAddition=false)
Change authentication data (e.g.
removeAuthenticationSessionData( $key)
Remove authentication data.
getAuthenticationRequests( $action, UserIdentity $user=null)
Return the applicable list of AuthenticationRequests.
continueAuthentication(array $reqs)
Continue an authentication flow.
canAuthenticateNow()
Indicate whether user authentication is possible.
continueAccountCreation(array $reqs)
Continue an account creation flow.
This is a value object for authentication requests.
getFieldInfo()
Fetch input field info.
This is a value object to hold authentication response data.
PSR-3 logger instance factory.
Value object representing a CSRF token.
Definition: Token.php:32
Variant of the Message class.
Definition: RawMessage.php:35
Parent class for all special pages.
Definition: SpecialPage.php:44
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,...
AuthManager null $authManager
Definition: SpecialPage.php:89
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:43
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
static wrap( $sv)
Succinct helper method to wrap a StatusValue.
Definition: Status.php:62