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,
60 array $requests, array $fieldInfo, array &$formDescriptor, $action
73 return $this->savedRequest ?: $this->
getContext()->getRequest();
86 protected function setRequest( array $data, $wasPosted =
null ) {
88 if ( $wasPosted ===
null ) {
89 $wasPosted = $request->wasPosted();
91 $this->savedRequest =
new DerivativeRequest( $request, $data + $request->getQueryValues(),
126 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
133 $authData = array_diff_key( $this->
getRequest()->getValues(),
134 $preservedParams, [
'title' => 1 ] );
145 $this->isReturn =
true;
165 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
168 if ( $securityLevel ) {
170 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
171 $queryParams = array_diff_key( $request->getQueryValues(), [
'title' =>
true ] );
173 if ( $request->wasPosted() ) {
175 $uniqueId = MWCryptRand::generateHex( 6 );
176 $key .=
':' . $uniqueId;
178 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
179 $authData = array_diff_key( $request->getValues(),
180 $this->getPreservedParams(
false ), [
'title' => 1 ] );
185 $url =
$title->getFullURL( [
186 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
188 'force' => $securityLevel,
193 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
194 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
198 $uniqueId = $request->getVal(
'authUniqueId' );
200 $key .=
':' . $uniqueId;
227 return array_key_exists( $defaultKey, static::$messages )
228 ? static::$messages[$defaultKey] : $defaultKey;
255 !$reset && $this->subPage ===
$subPage && $this->authAction
263 $this->authAction =
$authAction ?: $request->getText(
'authAction' );
264 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
266 if ( $request->wasPosted() ) {
268 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
269 $this->authAction = $continueAction;
275 $this->authAction, $this->
getUser() );
276 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
286 return in_array( $this->authAction, [
287 AuthManager::ACTION_LOGIN_CONTINUE,
288 AuthManager::ACTION_CREATE_CONTINUE,
289 AuthManager::ACTION_LINK_CONTINUE,
300 case AuthManager::ACTION_LOGIN:
301 $action = AuthManager::ACTION_LOGIN_CONTINUE;
303 case AuthManager::ACTION_CREATE:
304 $action = AuthManager::ACTION_CREATE_CONTINUE;
306 case AuthManager::ACTION_LINK:
307 $action = AuthManager::ACTION_LINK_CONTINUE;
323 if ( !in_array( $action, static::$allowedActions,
true ) ) {
324 throw new InvalidArgumentException(
'invalid action: ' . $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:
345 case AuthManager::ACTION_CHANGE:
346 case AuthManager::ACTION_REMOVE:
347 case AuthManager::ACTION_UNLINK:
351 throw new InvalidArgumentException(
'invalid action: ' . $action );
362 if ( !in_array( $action, static::$allowedActions,
true ) ) {
363 throw new InvalidArgumentException(
'invalid action: ' . $action );
371 case AuthManager::ACTION_LOGIN:
373 case AuthManager::ACTION_LOGIN_CONTINUE:
375 case AuthManager::ACTION_CREATE:
378 case AuthManager::ACTION_CREATE_CONTINUE:
380 case AuthManager::ACTION_LINK:
382 case AuthManager::ACTION_LINK_CONTINUE:
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' );
392 $req = reset( $requests );
394 $this->
getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
395 if ( !$status->isGood() ) {
396 return AuthenticationResponse::newFail( $status->getMessage() );
399 return AuthenticationResponse::newPass();
402 throw new InvalidArgumentException(
'invalid action: ' . $action );
419 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
420 $form->setSubmitCallback( [ $this,
'handleFormSubmit' ] );
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' ) );
434 $form->prepareForm();
435 $status = $form->trySubmit();
439 if ( $status ===
true ) {
441 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
442 } elseif ( $status ===
false ) {
444 } elseif ( $status instanceof
Status ) {
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 );
462 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
463 .
'first element of array is ' . gettype( reset( $status ) ) );
468 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
469 . gettype( $status ) );
472 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
477 LoggerFactory::getInstance(
'authentication' )
478 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
479 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
485 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
487 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
488 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
501 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
505 return Status::newGood( $response );
535 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
540 return $formDescriptor;
552 if ( $context->getRequest() !== $this->getRequest() ) {
557 $form = HTMLForm::factory(
'ooui', $formDescriptor, $context );
560 $form->addHiddenField(
'authAction', $this->authAction );
572 $status = Status::wrap( $status );
574 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
575 $form->prepareForm()->displayForm( $status );
590 $customSubmitButtonPresent =
false;
596 foreach ( $requests as $req ) {
597 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
599 $customSubmitButtonPresent =
true;
605 return !$customSubmitButtonPresent;
615 if ( $info[
'type'] ===
'button' ) {
629 foreach ( $formDescriptor as $field => &$definition ) {
631 if ( array_key_exists(
'class', $definition ) ) {
632 $class = $definition[
'class'];
633 } elseif ( array_key_exists(
'type', $definition ) ) {
634 $class = HTMLForm::$typeMappings[$definition[
'type']];
636 if ( $class !== HTMLInfoField::class ) {
637 $definition[
'tabindex'] = $i;
649 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
659 return 'wpAuthToken';
672 $formDescriptor = [];
673 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
677 $requestSnapshot =
serialize( $requests );
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' );
690 return $formDescriptor;
705 'name' => $fieldName,
708 if (
$type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
709 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
710 } elseif (
$type !==
'submit' ) {
711 $descriptor += array_filter( [
713 'label-message' => self::getField( $singleFieldInfo,
'label' ),
716 if ( isset( $singleFieldInfo[
'options'] ) ) {
717 $descriptor[
'options'] = array_flip( array_map(
static function ( $message ) {
719 return $message->parse();
720 }, $singleFieldInfo[
'options'] ) );
723 if ( isset( $singleFieldInfo[
'value'] ) ) {
724 $descriptor[
'default'] = $singleFieldInfo[
'value'];
727 if ( empty( $singleFieldInfo[
'optional'] ) ) {
728 $descriptor[
'required'] =
true;
743 foreach ( $formDescriptor as &$field ) {
744 $field[
'__index'] = $i++;
746 uasort( $formDescriptor,
function ( $first, $second ) {
748 ?: $first[
'__index'] <=> $second[
'__index'];
750 foreach ( $formDescriptor as &$field ) {
751 unset( $field[
'__index'] );
762 protected static function getField( array $array, $fieldName, $default =
null ) {
763 if ( array_key_exists( $fieldName, $array ) ) {
764 return $array[$fieldName];
779 'password' =>
'password',
780 'select' =>
'select',
781 'checkbox' =>
'check',
782 'multiselect' =>
'multiselect',
783 'button' =>
'submit',
784 'hidden' =>
'hidden',
787 if ( !array_key_exists(
$type, $map ) ) {
788 throw new \LogicException(
'invalid field type: ' .
$type );
806 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
809 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
813 !isset( $fieldInfo[$fieldName] )
815 !isset( $defaultField[
'baseField'] )
816 || !isset( $fieldInfo[$defaultField[
'baseField']] )
819 !isset( $defaultField[
'type'] )
820 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
823 $defaultFormDescriptor[$fieldName] =
null;
828 $requestField = $formDescriptor[$fieldName] ?? [];
830 isset( $defaultField[
'label'] )
831 || isset( $defaultField[
'label-message'] )
832 || isset( $defaultField[
'label-raw'] )
834 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
837 $defaultFormDescriptor[$fieldName] += $requestField;
840 return array_filter( $defaultFormDescriptor + $formDescriptor );
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-existend 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.
Variant of the Message class.
Parent class for all special pages.
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
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...