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 ] );
135 $authManager->setAuthenticationSessionData( $key, $authData );
142 $authData = $authManager->getAuthenticationSessionData( $key );
144 $authManager->removeAuthenticationSessionData( $key );
145 $this->isReturn =
true;
165 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
168 if ( $securityLevel ) {
169 $securityStatus = $authManager->securitySensitiveOperationStatus( $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 ] );
181 $authManager->setAuthenticationSessionData( $key, $authData );
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;
201 $authData = $authManager->getAuthenticationSessionData( $key );
203 $authManager->removeAuthenticationSessionData( $key );
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 );
329 : $authManager->getAuthenticationRequests( $action );
336 case AuthManager::ACTION_LOGIN:
337 case AuthManager::ACTION_LOGIN_CONTINUE:
338 return $authManager->canAuthenticateNow();
339 case AuthManager::ACTION_CREATE:
340 case AuthManager::ACTION_CREATE_CONTINUE:
341 return $authManager->canCreateAccounts();
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:
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:
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,
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' );
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() );
398 $authManager->changeAuthenticationData( $req );
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 ) ) ) {
455 $status = Status::newFatal( ...$status );
456 } elseif ( is_array( reset( $status ) ) ) {
457 $ret = Status::newGood();
458 foreach ( $status as $message ) {
460 $ret->fatal( ...$message );
464 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
465 .
'first element of array is ' . gettype( reset( $status ) ) );
470 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
471 . gettype( $status ) );
474 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
479 LoggerFactory::getInstance(
'authentication' )
480 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
481 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
487 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
489 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
490 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
503 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
507 return Status::newGood( $response );
537 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
542 return $formDescriptor;
554 if ( $context->getRequest() !== $this->getRequest() ) {
559 $form = HTMLForm::factory(
'ooui', $formDescriptor, $context );
562 $form->addHiddenField(
'authAction', $this->authAction );
574 $status = Status::wrap( $status );
576 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
577 $form->prepareForm()->displayForm( $status );
592 $customSubmitButtonPresent =
false;
598 foreach ( $requests as $req ) {
599 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
601 $customSubmitButtonPresent =
true;
607 return !$customSubmitButtonPresent;
617 if ( $info[
'type'] ===
'button' ) {
631 foreach ( $formDescriptor as $field => &$definition ) {
633 if ( array_key_exists(
'class', $definition ) ) {
634 $class = $definition[
'class'];
635 } elseif ( array_key_exists(
'type', $definition ) ) {
636 $class = HTMLForm::$typeMappings[$definition[
'type']];
638 if ( $class !== HTMLInfoField::class ) {
639 $definition[
'tabindex'] = $i;
651 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
661 return 'wpAuthToken';
674 $formDescriptor = [];
675 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
679 $requestSnapshot =
serialize( $requests );
681 $this->
getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
682 $formDescriptor, $action );
683 if ( $requestSnapshot !==
serialize( $requests ) ) {
684 LoggerFactory::getInstance(
'authentication' )->warning(
685 'AuthChangeFormFields hook changed auth requests' );
692 return $formDescriptor;
707 'name' => $fieldName,
710 if (
$type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
711 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
712 } elseif (
$type !==
'submit' ) {
713 $descriptor += array_filter( [
715 'label-message' => self::getField( $singleFieldInfo,
'label' ),
718 if ( isset( $singleFieldInfo[
'options'] ) ) {
719 $descriptor[
'options'] = array_flip( array_map(
static function ( $message ) {
721 return $message->parse();
722 }, $singleFieldInfo[
'options'] ) );
725 if ( isset( $singleFieldInfo[
'value'] ) ) {
726 $descriptor[
'default'] = $singleFieldInfo[
'value'];
729 if ( empty( $singleFieldInfo[
'optional'] ) ) {
730 $descriptor[
'required'] =
true;
745 foreach ( $formDescriptor as &$field ) {
746 $field[
'__index'] = $i++;
748 uasort( $formDescriptor,
function ( $first, $second ) {
750 ?: $first[
'__index'] <=> $second[
'__index'];
752 foreach ( $formDescriptor as &$field ) {
753 unset( $field[
'__index'] );
764 protected static function getField( array $array, $fieldName, $default =
null ) {
765 if ( array_key_exists( $fieldName, $array ) ) {
766 return $array[$fieldName];
781 'password' =>
'password',
782 'select' =>
'select',
783 'checkbox' =>
'check',
784 'multiselect' =>
'multiselect',
785 'button' =>
'submit',
786 'hidden' =>
'hidden',
789 if ( !array_key_exists(
$type, $map ) ) {
790 throw new \LogicException(
'invalid field type: ' .
$type );
808 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
811 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
815 !isset( $fieldInfo[$fieldName] )
817 !isset( $defaultField[
'baseField'] )
818 || !isset( $fieldInfo[$defaultField[
'baseField']] )
821 !isset( $defaultField[
'type'] )
822 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
825 $defaultFormDescriptor[$fieldName] =
null;
830 $requestField = $formDescriptor[$fieldName] ?? [];
832 isset( $defaultField[
'label'] )
833 || isset( $defaultField[
'label-message'] )
834 || isset( $defaultField[
'label-raw'] )
836 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
839 $defaultFormDescriptor[$fieldName] += $requestField;
842 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-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.
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,...
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...