5use InvalidArgumentException;
28use UnexpectedValueException;
45 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
46 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
47 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
48 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
85 array $requests, array $fieldInfo, array &$formDescriptor, $action
99 return $this->savedRequest ?: $this->
getContext()->getRequest();
112 protected function setRequest( array $data, $wasPosted =
null ) {
114 $this->isFakePostRequest = $wasPosted ===
true &&
$request->wasPosted() ===
false;
148 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
150 if ( $subPage ===
'return' ) {
155 $authData = array_diff_key( $this->
getRequest()->getValues(),
156 $preservedParams, [
'title' => 1 ] );
157 $uniqueId = MWCryptRand::generateHex( 6 );
158 $preservedParams[
'authUniqueId'] = $uniqueId;
159 $key .=
':' . $uniqueId;
160 $authManager->setAuthenticationSessionData( $key, $authData );
165 } elseif ( $this->
getRequest()->getCheck(
'authUniqueId' ) ) {
166 $uniqueId = $this->
getRequest()->getVal(
'authUniqueId' );
167 $key .=
':' . $uniqueId;
168 $authData = $authManager->getAuthenticationSessionData( $key );
170 $authManager->removeAuthenticationSessionData( $key );
171 $this->isReturn =
true;
173 $this->setPostTransactionProfilerExpectations( __METHOD__ );
193 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
196 if ( $securityLevel ) {
197 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
198 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
199 $queryParams = array_diff_key(
$request->getQueryValues(), [
'title' =>
true ] );
203 $uniqueId = MWCryptRand::generateHex( 6 );
204 $key .=
':' . $uniqueId;
206 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
207 $authData = array_diff_key(
$request->getValues(),
208 $this->getPreservedParams(), [
'title' => 1 ] );
209 $authManager->setAuthenticationSessionData( $key, $authData );
214 $keepParams = [
'uselang',
'useskin',
'useformat',
'variant',
'debug',
'safemode' ];
217 $url = $title->getFullURL( [
218 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
220 'force' => $securityLevel,
221 ] + array_intersect_key( $queryParams, array_fill_keys( $keepParams,
true ) ),
false,
PROTO_HTTPS );
227 if ( $securityStatus !== AuthManager::SEC_OK ) {
228 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
232 $uniqueId =
$request->getVal(
'authUniqueId' );
234 $key .=
':' . $uniqueId;
235 $authData = $authManager->getAuthenticationSessionData( $key );
237 $authManager->removeAuthenticationSessionData( $key );
239 $this->setPostTransactionProfilerExpectations( __METHOD__ );
246 private function setPostTransactionProfilerExpectations(
string $fname ) {
248 $trxProfiler = Profiler::instance()->getTransactionProfiler();
249 $trxProfiler->redefineExpectations( $trxLimits[
'POST'], $fname );
250 DeferredUpdates::addCallableUpdate(
static function () use ( $trxProfiler, $trxLimits, $fname ) {
251 $trxProfiler->redefineExpectations( $trxLimits[
'PostSend-POST'], $fname );
271 return array_key_exists( $defaultKey, static::$messages )
272 ? static::$messages[$defaultKey] : $defaultKey;
297 return $this->
getAuthManager()->getAuthenticationRequests( $action,
316 !$reset && $this->subPage ===
$subPage && $this->authAction
325 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
329 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
330 $this->authAction = $continueAction;
336 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
346 return in_array( $this->authAction, [
347 AuthManager::ACTION_LOGIN_CONTINUE,
348 AuthManager::ACTION_CREATE_CONTINUE,
349 AuthManager::ACTION_LINK_CONTINUE,
360 case AuthManager::ACTION_LOGIN:
361 $action = AuthManager::ACTION_LOGIN_CONTINUE;
363 case AuthManager::ACTION_CREATE:
364 $action = AuthManager::ACTION_CREATE_CONTINUE;
366 case AuthManager::ACTION_LINK:
367 $action = AuthManager::ACTION_LINK_CONTINUE;
382 if ( !in_array( $action, static::$allowedActions,
true ) ) {
383 throw new InvalidArgumentException(
'invalid action: ' . $action );
395 case AuthManager::ACTION_LOGIN:
396 case AuthManager::ACTION_LOGIN_CONTINUE:
397 return $authManager->canAuthenticateNow();
398 case AuthManager::ACTION_CREATE:
399 case AuthManager::ACTION_CREATE_CONTINUE:
400 return $authManager->canCreateAccounts();
401 case AuthManager::ACTION_LINK:
402 case AuthManager::ACTION_LINK_CONTINUE:
403 return $authManager->canLinkAccounts();
404 case AuthManager::ACTION_CHANGE:
405 case AuthManager::ACTION_REMOVE:
406 case AuthManager::ACTION_UNLINK:
410 throw new InvalidArgumentException(
'invalid action: ' . $action );
421 if ( !in_array( $action, static::$allowedActions,
true ) ) {
422 throw new InvalidArgumentException(
'invalid action: ' . $action );
430 case AuthManager::ACTION_LOGIN:
431 return $authManager->beginAuthentication( $requests, $returnToUrl );
432 case AuthManager::ACTION_LOGIN_CONTINUE:
433 return $authManager->continueAuthentication( $requests );
434 case AuthManager::ACTION_CREATE:
435 return $authManager->beginAccountCreation( $this->
getAuthority(), $requests,
437 case AuthManager::ACTION_CREATE_CONTINUE:
438 return $authManager->continueAccountCreation( $requests );
439 case AuthManager::ACTION_LINK:
440 return $authManager->beginAccountLink( $this->
getUser(), $requests, $returnToUrl );
441 case AuthManager::ACTION_LINK_CONTINUE:
442 return $authManager->continueAccountLink( $requests );
443 case AuthManager::ACTION_CHANGE:
444 case AuthManager::ACTION_REMOVE:
445 case AuthManager::ACTION_UNLINK:
446 if ( count( $requests ) > 1 ) {
447 throw new InvalidArgumentException(
'only one auth request can be changed at a time' );
451 throw new InvalidArgumentException(
'no auth request' );
453 $req = reset( $requests );
454 $status = $authManager->allowsAuthenticationDataChange( $req );
455 $this->
getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
456 if ( !$status->isGood() ) {
457 return AuthenticationResponse::newFail( $status->getMessage() );
459 $authManager->changeAuthenticationData( $req );
460 return AuthenticationResponse::newPass();
463 throw new InvalidArgumentException(
'invalid action: ' . $action );
480 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
487 if ( $sessionToken->wasNew() && ( !$this->isFakePostRequest || $this->isReturn ) ) {
489 return Status::newFatal( $this->
messageKey(
'authform-newtoken' ) );
490 } elseif ( !$requestTokenValue ) {
491 return Status::newFatal( $this->
messageKey(
'authform-notoken' ) );
492 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
493 return Status::newFatal( $this->
messageKey(
'authform-wrongtoken' ) );
496 $form->prepareForm();
497 $status = $form->trySubmit();
501 if ( $status ===
true ) {
503 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
504 } elseif ( $status ===
false ) {
506 } elseif ( $status instanceof
Status ) {
511 $status = Status::wrap( $status );
512 } elseif ( is_string( $status ) ) {
513 $status = Status::newFatal(
new RawMessage(
'$1', [ $status ] ) );
514 } elseif ( is_array( $status ) ) {
515 if ( is_string( reset( $status ) ) ) {
517 $status = Status::newFatal( ...$status );
518 } elseif ( is_array( reset( $status ) ) ) {
519 $ret = Status::newGood();
520 foreach ( $status as $message ) {
522 $ret->fatal( ...$message );
526 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
527 .
'first element of array is ' . get_debug_type( reset( $status ) ) );
532 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
533 . get_debug_type( $status ) );
536 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
541 LoggerFactory::getInstance(
'authentication' )
542 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
543 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
549 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
551 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
552 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
565 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
569 return Status::newGood( $response );
594 if ( is_bool( $options ) ) {
595 wfDeprecated( __METHOD__ .
' boolean $options',
'1.43' );
596 $options = [
'withToken' => $options ];
600 'withToken' =>
false,
603 $params = $options[
'params'] ?? [];
604 if ( !$options[
'reset'] && $this->authAction !== $this->
getDefaultAction( $this->subPage ) ) {
607 if ( $options[
'withToken'] ) {
612 return $loginHelper->getPreservedParams( [
'reset' => $options[
'reset'],
'params' => $params ] );
623 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
628 return $formDescriptor;
640 if ( $context->getRequest() !== $this->getRequest() ) {
645 $form = HTMLForm::factory(
'ooui', $formDescriptor, $context );
648 $form->addHiddenField(
'authAction', $this->authAction );
660 $status = Status::wrap( $status );
662 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
663 $form->prepareForm()->displayForm( $status );
678 $customSubmitButtonPresent =
false;
684 foreach ( $requests as $req ) {
685 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
687 $customSubmitButtonPresent =
true;
693 return !$customSubmitButtonPresent;
703 if ( $info[
'type'] ===
'button' ) {
717 foreach ( $formDescriptor as &$definition ) {
719 if ( array_key_exists(
'class', $definition ) ) {
720 $class = $definition[
'class'];
721 } elseif ( array_key_exists(
'type', $definition ) ) {
722 $class = HTMLForm::$typeMappings[$definition[
'type']];
724 if ( $class !== HTMLInfoField::class ) {
725 $definition[
'tabindex'] = $i;
737 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
747 return 'wpAuthToken';
760 $formDescriptor = [];
761 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
765 $requestSnapshot = serialize( $requests );
767 $this->
getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
768 $formDescriptor, $action );
769 if ( $requestSnapshot !== serialize( $requests ) ) {
770 LoggerFactory::getInstance(
'authentication' )->warning(
771 'AuthChangeFormFields hook changed auth requests' );
778 return $formDescriptor;
793 'name' => $fieldName,
796 if ( $type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
797 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
798 } elseif ( $type !==
'submit' ) {
799 $descriptor += array_filter( [
801 'label-message' => self::getField( $singleFieldInfo,
'label' ),
804 if ( isset( $singleFieldInfo[
'options'] ) ) {
805 $descriptor[
'options'] = array_flip( array_map(
static function ( $message ) {
807 return $message->parse();
808 }, $singleFieldInfo[
'options'] ) );
811 if ( isset( $singleFieldInfo[
'value'] ) ) {
812 $descriptor[
'default'] = $singleFieldInfo[
'value'];
815 if ( empty( $singleFieldInfo[
'optional'] ) ) {
816 $descriptor[
'required'] =
true;
830 foreach ( $formDescriptor as &$field ) {
831 $field[
'__index'] = $i++;
834 uasort( $formDescriptor,
static function ( $first, $second ) {
836 ?: $first[
'__index'] <=> $second[
'__index'];
838 foreach ( $formDescriptor as &$field ) {
839 unset( $field[
'__index'] );
850 protected static function getField( array $array, $fieldName, $default =
null ) {
851 if ( array_key_exists( $fieldName, $array ) ) {
852 return $array[$fieldName];
868 'password' =>
'password',
869 'select' =>
'select',
870 'checkbox' =>
'check',
871 'multiselect' =>
'multiselect',
872 'button' =>
'submit',
873 'hidden' =>
'hidden',
876 if ( !array_key_exists( $type, $map ) ) {
877 throw new InvalidArgumentException(
'invalid field type: ' . $type );
895 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
898 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
902 !isset( $fieldInfo[$fieldName] )
904 !isset( $defaultField[
'baseField'] )
905 || !isset( $fieldInfo[$defaultField[
'baseField']] )
908 !isset( $defaultField[
'type'] )
909 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
912 $defaultFormDescriptor[$fieldName] =
null;
917 $requestField = $formDescriptor[$fieldName] ?? [];
919 isset( $defaultField[
'label'] )
920 || isset( $defaultField[
'label-message'] )
921 || isset( $defaultField[
'label-raw'] )
923 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
926 $defaultFormDescriptor[$fieldName] += $requestField;
929 return array_filter( $defaultFormDescriptor + $formDescriptor );
934class_alias( AuthManagerSpecialPage::class,
'AuthManagerSpecialPage' );
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
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.
A class containing constants representing the names of configuration variables.
const TrxProfilerLimits
Name constant for the TrxProfilerLimits setting, for use with Config::get()
A special page subclass for authentication-related special pages.
AuthenticationRequest[] $authRequests
string $subPage
Subpage of the special page.
performAuthenticationStep( $action, array $requests)
getToken()
Returns the CSRF token.
bool $isReturn
True if the current request is a result of returning from a redirect flow.
static array $messages
Customized messages.
handleReturnBeforeExecute( $subPage)
Handle redirection from the /return subpage.
static mapFieldInfoTypeToFormDescriptorType( $type)
Maps AuthenticationRequest::getFieldInfo() types to HTMLForm types.
getAuthFormDescriptor( $requests, $action)
Generates a HTMLForm descriptor array from a set of authentication requests.
getAuthForm(array $requests, $action)
setRequest(array $data, $wasPosted=null)
Override the POST data, GET data from the real request is preserved.
static getField(array $array, $fieldName, $default=null)
Get an array value, or a default if it does not exist.
displayForm( $status)
Display the form.
getContinueAction( $action)
Gets the _CONTINUE version of an action.
static sortFormDescriptorFields(array &$formDescriptor)
Sort the fields of a form descriptor by their 'weight' property.
onAuthChangeFormFields(array $requests, array $fieldInfo, array &$formDescriptor, $action)
Change the form descriptor that determines how a field will look in the authentication form.
static mapSingleFieldInfo( $singleFieldInfo, $fieldName)
Maps an authentication field configuration for a single field (as returned by AuthenticationRequest::...
isContinued()
Returns true if this is not the first step of the authentication.
needsSubmitButton(array $requests)
Returns true if the form built from the given AuthenticationRequests needs a submit button.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
string $authAction
one of the AuthManager::ACTION_* constants.
messageKey( $defaultKey)
Return custom message key.
static string[] $allowedActions
The list of actions this special page deals with.
static mergeDefaultFormDescriptor(array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor)
Apply defaults to a form descriptor, without creating non-existent fields.
isActionAllowed( $action)
Checks whether AuthManager is ready to perform the action.
beforeExecute( $subPage)
Gets called before execute.Return false to prevent calling execute() (since 1.27+)....
getRequestBlacklist()
Allows blacklisting certain request types.
trySubmit()
Attempts to do an authentication step with the submitted data.
loadAuth( $subPage, $authAction=null, $reset=false)
Load or initialize $authAction, $authRequests and $subPage.
getTokenName()
Returns the name of the CSRF token (under which it should be found in the POST or GET data).
addTabIndex(&$formDescriptor)
Adds a sequential tabindex starting from 1 to all form elements.
getPreservedParams( $options=[])
Returns URL query parameters which should be preserved between authentication requests.
fieldInfoToFormDescriptor(array $requests, array $fieldInfo, $action)
Turns a field info array into a form descriptor.
handleFormSubmit( $data)
Submit handler callback for HTMLForm.
bool $isFakePostRequest
Set when we're pretending that we got a POST request during redirect flows.
getDefaultAction( $subPage)
Get the default action for this special page if none is given via URL/POST data.
getAuthenticationRequests( $action, ?UserIdentity $user=null)
Get the list of AuthenticationRequests from the AuthManager.
hasOwnSubmitButton(AuthenticationRequest $req)
Checks whether the given AuthenticationRequest has its own submit button.
getRequest()
Get the WebRequest being used for this instance.WebRequest 1.18
WebRequest null $savedRequest
If set, will be used instead of the real request.
Parent class for all special pages.
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,...
getUser()
Shortcut to get the User executing this instance.
getPageTitle( $subpage=false)
Get a self-referential title object.
getConfig()
Shortcut to get main config object.
getContext()
Gets the context this SpecialPage is executed in.
getOutput()
Get the OutputPage being used for this instance.
getAuthority()
Shortcut to get the Authority executing this instance.
getName()
Get the canonical, unlocalized name of this special page without namespace.
getFullTitle()
Return the full title, including $par.
Generic operation result class Has warning/error list, boolean status and arbitrary value.