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,
62 array $requests, array $fieldInfo, array &$formDescriptor, $action
75 return $this->savedRequest ?: $this->
getContext()->getRequest();
88 protected function setRequest( array $data, $wasPosted =
null ) {
92 $data + $request->getQueryValues(),
93 $wasPosted ?? $request->wasPosted()
128 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
135 $authData = array_diff_key( $this->
getRequest()->getValues(),
136 $preservedParams, [
'title' => 1 ] );
137 $authManager->setAuthenticationSessionData( $key, $authData );
144 $authData = $authManager->getAuthenticationSessionData( $key );
146 $authManager->removeAuthenticationSessionData( $key );
147 $this->isReturn =
true;
167 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
170 if ( $securityLevel ) {
171 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
172 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
173 $queryParams = array_diff_key( $request->getQueryValues(), [
'title' =>
true ] );
175 if ( $request->wasPosted() ) {
178 $key .=
':' . $uniqueId;
180 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
181 $authData = array_diff_key( $request->getValues(),
182 $this->getPreservedParams(
false ), [
'title' => 1 ] );
183 $authManager->setAuthenticationSessionData( $key, $authData );
187 $url =
$title->getFullURL( [
188 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
190 'force' => $securityLevel,
195 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
196 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
200 $uniqueId = $request->getVal(
'authUniqueId' );
202 $key .=
':' . $uniqueId;
203 $authData = $authManager->getAuthenticationSessionData( $key );
205 $authManager->removeAuthenticationSessionData( $key );
229 return array_key_exists( $defaultKey, static::$messages )
230 ? static::$messages[$defaultKey] : $defaultKey;
257 !$reset && $this->subPage ===
$subPage && $this->authAction
265 $this->authAction =
$authAction ?: $request->getText(
'authAction' );
266 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
268 if ( $request->wasPosted() ) {
270 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
271 $this->authAction = $continueAction;
277 $this->authAction, $this->
getUser() );
278 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
288 return in_array( $this->authAction, [
289 AuthManager::ACTION_LOGIN_CONTINUE,
290 AuthManager::ACTION_CREATE_CONTINUE,
291 AuthManager::ACTION_LINK_CONTINUE,
302 case AuthManager::ACTION_LOGIN:
303 $action = AuthManager::ACTION_LOGIN_CONTINUE;
305 case AuthManager::ACTION_CREATE:
306 $action = AuthManager::ACTION_CREATE_CONTINUE;
308 case AuthManager::ACTION_LINK:
309 $action = AuthManager::ACTION_LINK_CONTINUE;
325 if ( !in_array( $action, static::$allowedActions,
true ) ) {
326 throw new InvalidArgumentException(
'invalid action: ' . $action );
331 : $authManager->getAuthenticationRequests( $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:
353 throw new InvalidArgumentException(
'invalid action: ' . $action );
364 if ( !in_array( $action, static::$allowedActions,
true ) ) {
365 throw new InvalidArgumentException(
'invalid action: ' . $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,
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' );
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() );
400 $authManager->changeAuthenticationData( $req );
401 return AuthenticationResponse::newPass();
404 throw new InvalidArgumentException(
'invalid action: ' . $action );
421 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
422 $form->setSubmitCallback( [ $this,
'handleFormSubmit' ] );
428 if ( $sessionToken->wasNew() ) {
430 } elseif ( !$requestTokenValue ) {
432 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
436 $form->prepareForm();
437 $status = $form->trySubmit();
441 if ( $status ===
true ) {
443 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
444 } elseif ( $status ===
false ) {
446 } elseif ( $status instanceof
Status ) {
452 } elseif ( is_string( $status ) ) {
454 } elseif ( is_array( $status ) ) {
455 if ( is_string( reset( $status ) ) ) {
458 } elseif ( is_array( reset( $status ) ) ) {
460 foreach ( $status as $message ) {
462 $ret->fatal( ...$message );
466 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
467 .
'first element of array is ' . gettype( reset( $status ) ) );
472 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
473 . gettype( $status ) );
476 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
481 LoggerFactory::getInstance(
'authentication' )
482 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
483 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
489 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
491 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
492 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
505 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
539 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
544 return $formDescriptor;
556 if ( $context->getRequest() !== $this->getRequest() ) {
564 $form->addHiddenField(
'authAction', $this->authAction );
578 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
579 $form->prepareForm()->displayForm( $status );
594 $customSubmitButtonPresent =
false;
600 foreach ( $requests as $req ) {
601 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
603 $customSubmitButtonPresent =
true;
609 return !$customSubmitButtonPresent;
619 if ( $info[
'type'] ===
'button' ) {
633 foreach ( $formDescriptor as &$definition ) {
635 if ( array_key_exists(
'class', $definition ) ) {
636 $class = $definition[
'class'];
637 } elseif ( array_key_exists(
'type', $definition ) ) {
640 if ( $class !== HTMLInfoField::class ) {
641 $definition[
'tabindex'] = $i;
653 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
663 return 'wpAuthToken';
676 $formDescriptor = [];
677 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
681 $requestSnapshot = serialize( $requests );
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' );
694 return $formDescriptor;
709 'name' => $fieldName,
712 if (
$type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
713 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
714 } elseif (
$type !==
'submit' ) {
715 $descriptor += array_filter( [
717 'label-message' => self::getField( $singleFieldInfo,
'label' ),
720 if ( isset( $singleFieldInfo[
'options'] ) ) {
721 $descriptor[
'options'] = array_flip( array_map(
static function ( $message ) {
723 return $message->parse();
724 }, $singleFieldInfo[
'options'] ) );
727 if ( isset( $singleFieldInfo[
'value'] ) ) {
728 $descriptor[
'default'] = $singleFieldInfo[
'value'];
731 if ( empty( $singleFieldInfo[
'optional'] ) ) {
732 $descriptor[
'required'] =
true;
747 foreach ( $formDescriptor as &$field ) {
748 $field[
'__index'] = $i++;
750 uasort( $formDescriptor,
function ( $first, $second ) {
752 ?: $first[
'__index'] <=> $second[
'__index'];
754 foreach ( $formDescriptor as &$field ) {
755 unset( $field[
'__index'] );
766 protected static function getField( array $array, $fieldName, $default =
null ) {
767 if ( array_key_exists( $fieldName, $array ) ) {
768 return $array[$fieldName];
783 'password' =>
'password',
784 'select' =>
'select',
785 'checkbox' =>
'check',
786 'multiselect' =>
'multiselect',
787 'button' =>
'submit',
788 'hidden' =>
'hidden',
791 if ( !array_key_exists(
$type, $map ) ) {
792 throw new \LogicException(
'invalid field type: ' .
$type );
810 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
813 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
817 !isset( $fieldInfo[$fieldName] )
819 !isset( $defaultField[
'baseField'] )
820 || !isset( $fieldInfo[$defaultField[
'baseField']] )
823 !isset( $defaultField[
'type'] )
824 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
827 $defaultFormDescriptor[$fieldName] =
null;
832 $requestField = $formDescriptor[$fieldName] ?? [];
834 isset( $defaultField[
'label'] )
835 || isset( $defaultField[
'label-message'] )
836 || isset( $defaultField[
'label-raw'] )
838 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
841 $defaultFormDescriptor[$fieldName] += $requestField;
844 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 ...
An error page which can definitely be safely rendered using the OutputPage.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
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.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
static newGood( $value=null)
Factory function for good results.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static wrap( $sv)
Succinct helper method to wrap a StatusValue.