6use InvalidArgumentException;
23use UnexpectedValueException;
40 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
41 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
42 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
43 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
77 array $requests, array $fieldInfo, array &$formDescriptor, $action
90 return $this->savedRequest ?: $this->
getContext()->getRequest();
103 protected function setRequest( array $data, $wasPosted =
null ) {
107 $data + $request->getQueryValues(),
108 $wasPosted ?? $request->wasPosted()
143 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
150 $authData = array_diff_key( $this->
getRequest()->getValues(),
151 $preservedParams, [
'title' => 1 ] );
152 $authManager->setAuthenticationSessionData( $key, $authData );
159 $authData = $authManager->getAuthenticationSessionData( $key );
161 $authManager->removeAuthenticationSessionData( $key );
162 $this->isReturn =
true;
182 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
185 if ( $securityLevel ) {
186 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
187 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
188 $queryParams = array_diff_key( $request->getQueryValues(), [
'title' =>
true ] );
190 if ( $request->wasPosted() ) {
192 $uniqueId = MWCryptRand::generateHex( 6 );
193 $key .=
':' . $uniqueId;
195 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
196 $authData = array_diff_key( $request->getValues(),
197 $this->getPreservedParams(), [
'title' => 1 ] );
198 $authManager->setAuthenticationSessionData( $key, $authData );
202 $url = $title->getFullURL( [
203 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
205 'force' => $securityLevel,
212 if ( $securityStatus !== AuthManager::SEC_OK ) {
213 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
217 $uniqueId = $request->getVal(
'authUniqueId' );
219 $key .=
':' . $uniqueId;
220 $authData = $authManager->getAuthenticationSessionData( $key );
222 $authManager->removeAuthenticationSessionData( $key );
246 return array_key_exists( $defaultKey, static::$messages )
247 ? static::$messages[$defaultKey] : $defaultKey;
274 !$reset && $this->subPage ===
$subPage && $this->authAction
282 $this->authAction =
$authAction ?: $request->getText(
'authAction' );
283 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
285 if ( $request->wasPosted() ) {
287 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
288 $this->authAction = $continueAction;
294 $this->authAction, $this->
getUser() );
295 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
305 return in_array( $this->authAction, [
306 AuthManager::ACTION_LOGIN_CONTINUE,
307 AuthManager::ACTION_CREATE_CONTINUE,
308 AuthManager::ACTION_LINK_CONTINUE,
319 case AuthManager::ACTION_LOGIN:
320 $action = AuthManager::ACTION_LOGIN_CONTINUE;
322 case AuthManager::ACTION_CREATE:
323 $action = AuthManager::ACTION_CREATE_CONTINUE;
325 case AuthManager::ACTION_LINK:
326 $action = AuthManager::ACTION_LINK_CONTINUE;
341 if ( !in_array( $action, static::$allowedActions,
true ) ) {
342 throw new InvalidArgumentException(
'invalid action: ' . $action );
347 : $authManager->getAuthenticationRequests( $action );
354 case AuthManager::ACTION_LOGIN:
355 case AuthManager::ACTION_LOGIN_CONTINUE:
356 return $authManager->canAuthenticateNow();
357 case AuthManager::ACTION_CREATE:
358 case AuthManager::ACTION_CREATE_CONTINUE:
359 return $authManager->canCreateAccounts();
360 case AuthManager::ACTION_LINK:
361 case AuthManager::ACTION_LINK_CONTINUE:
362 return $authManager->canLinkAccounts();
363 case AuthManager::ACTION_CHANGE:
364 case AuthManager::ACTION_REMOVE:
365 case AuthManager::ACTION_UNLINK:
369 throw new InvalidArgumentException(
'invalid action: ' . $action );
380 if ( !in_array( $action, static::$allowedActions,
true ) ) {
381 throw new InvalidArgumentException(
'invalid action: ' . $action );
389 case AuthManager::ACTION_LOGIN:
390 return $authManager->beginAuthentication( $requests, $returnToUrl );
391 case AuthManager::ACTION_LOGIN_CONTINUE:
392 return $authManager->continueAuthentication( $requests );
393 case AuthManager::ACTION_CREATE:
394 return $authManager->beginAccountCreation( $this->
getAuthority(), $requests,
396 case AuthManager::ACTION_CREATE_CONTINUE:
397 return $authManager->continueAccountCreation( $requests );
398 case AuthManager::ACTION_LINK:
399 return $authManager->beginAccountLink( $this->
getUser(), $requests, $returnToUrl );
400 case AuthManager::ACTION_LINK_CONTINUE:
401 return $authManager->continueAccountLink( $requests );
402 case AuthManager::ACTION_CHANGE:
403 case AuthManager::ACTION_REMOVE:
404 case AuthManager::ACTION_UNLINK:
405 if ( count( $requests ) > 1 ) {
406 throw new InvalidArgumentException(
'only one auth request can be changed at a time' );
410 throw new InvalidArgumentException(
'no auth request' );
412 $req = reset( $requests );
413 $status = $authManager->allowsAuthenticationDataChange( $req );
414 $this->
getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
415 if ( !$status->isGood() ) {
416 return AuthenticationResponse::newFail( $status->getMessage() );
418 $authManager->changeAuthenticationData( $req );
419 return AuthenticationResponse::newPass();
422 throw new InvalidArgumentException(
'invalid action: ' . $action );
439 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
440 $form->setSubmitCallback( [ $this,
'handleFormSubmit' ] );
446 if ( $sessionToken->wasNew() ) {
447 return Status::newFatal( $this->
messageKey(
'authform-newtoken' ) );
448 } elseif ( !$requestTokenValue ) {
449 return Status::newFatal( $this->
messageKey(
'authform-notoken' ) );
450 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
451 return Status::newFatal( $this->
messageKey(
'authform-wrongtoken' ) );
454 $form->prepareForm();
455 $status = $form->trySubmit();
459 if ( $status ===
true ) {
461 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
462 } elseif ( $status ===
false ) {
464 } elseif ( $status instanceof
Status ) {
469 $status = Status::wrap( $status );
470 } elseif ( is_string( $status ) ) {
471 $status = Status::newFatal(
new RawMessage(
'$1', [ $status ] ) );
472 } elseif ( is_array( $status ) ) {
473 if ( is_string( reset( $status ) ) ) {
475 $status = Status::newFatal( ...$status );
476 } elseif ( is_array( reset( $status ) ) ) {
477 $ret = Status::newGood();
478 foreach ( $status as $message ) {
480 $ret->fatal( ...$message );
484 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
485 .
'first element of array is ' . get_debug_type( reset( $status ) ) );
490 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
491 . get_debug_type( $status ) );
494 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
499 LoggerFactory::getInstance(
'authentication' )
500 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
501 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
507 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
509 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
510 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
523 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
527 return Status::newGood( $response );
551 if ( is_bool( $options ) ) {
552 wfDeprecated( __METHOD__ .
' boolean $options',
'1.43' );
553 $options = [
'withToken' => $options ];
557 'withToken' =>
false,
560 '@phan-var array{reset: bool, withToken: bool} $options';
565 'uselang' => $request->getVal(
'uselang' ),
566 'variant' => $request->getVal(
'variant' ),
567 'returnto' => $request->getVal(
'returnto' ),
568 'returntoquery' => $request->getVal(
'returntoquery' ),
569 'returntoanchor' => $request->getVal(
'returntoanchor' ),
572 if ( !$options[
'reset'] && $this->authAction !== $this->
getDefaultAction( $this->subPage ) ) {
576 if ( $options[
'withToken'] ) {
583 $params, [
'reset' => $options[
'reset'] ]
586 return array_filter(
$params, fn ( $val ) => $val !==
null );
597 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
602 return $formDescriptor;
614 if ( $context->getRequest() !== $this->getRequest() ) {
619 $form = HTMLForm::factory(
'ooui', $formDescriptor, $context );
622 $form->addHiddenField(
'authAction', $this->authAction );
634 $status = Status::wrap( $status );
636 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
637 $form->prepareForm()->displayForm( $status );
652 $customSubmitButtonPresent =
false;
658 foreach ( $requests as $req ) {
659 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
661 $customSubmitButtonPresent =
true;
667 return !$customSubmitButtonPresent;
677 if ( $info[
'type'] ===
'button' ) {
691 foreach ( $formDescriptor as &$definition ) {
693 if ( array_key_exists(
'class', $definition ) ) {
694 $class = $definition[
'class'];
695 } elseif ( array_key_exists(
'type', $definition ) ) {
696 $class = HTMLForm::$typeMappings[$definition[
'type']];
698 if ( $class !== HTMLInfoField::class ) {
699 $definition[
'tabindex'] = $i;
711 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
721 return 'wpAuthToken';
734 $formDescriptor = [];
735 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
739 $requestSnapshot = serialize( $requests );
741 $this->
getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
742 $formDescriptor, $action );
743 if ( $requestSnapshot !== serialize( $requests ) ) {
744 LoggerFactory::getInstance(
'authentication' )->warning(
745 'AuthChangeFormFields hook changed auth requests' );
752 return $formDescriptor;
767 'name' => $fieldName,
770 if ( $type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
771 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
772 } elseif ( $type !==
'submit' ) {
773 $descriptor += array_filter( [
775 'label-message' => self::getField( $singleFieldInfo,
'label' ),
778 if ( isset( $singleFieldInfo[
'options'] ) ) {
779 $descriptor[
'options'] = array_flip( array_map(
static function ( $message ) {
781 return $message->parse();
782 }, $singleFieldInfo[
'options'] ) );
785 if ( isset( $singleFieldInfo[
'value'] ) ) {
786 $descriptor[
'default'] = $singleFieldInfo[
'value'];
789 if ( empty( $singleFieldInfo[
'optional'] ) ) {
790 $descriptor[
'required'] =
true;
805 foreach ( $formDescriptor as &$field ) {
806 $field[
'__index'] = $i++;
809 uasort( $formDescriptor,
static function ( $first, $second ) {
811 ?: $first[
'__index'] <=> $second[
'__index'];
813 foreach ( $formDescriptor as &$field ) {
814 unset( $field[
'__index'] );
825 protected static function getField( array $array, $fieldName, $default =
null ) {
826 if ( array_key_exists( $fieldName, $array ) ) {
827 return $array[$fieldName];
843 'password' =>
'password',
844 'select' =>
'select',
845 'checkbox' =>
'check',
846 'multiselect' =>
'multiselect',
847 'button' =>
'submit',
848 'hidden' =>
'hidden',
851 if ( !array_key_exists( $type, $map ) ) {
852 throw new InvalidArgumentException(
'invalid field type: ' . $type );
870 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
873 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
877 !isset( $fieldInfo[$fieldName] )
879 !isset( $defaultField[
'baseField'] )
880 || !isset( $fieldInfo[$defaultField[
'baseField']] )
883 !isset( $defaultField[
'type'] )
884 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
887 $defaultFormDescriptor[$fieldName] =
null;
892 $requestField = $formDescriptor[$fieldName] ?? [];
894 isset( $defaultField[
'label'] )
895 || isset( $defaultField[
'label-message'] )
896 || isset( $defaultField[
'label-raw'] )
898 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
901 $defaultFormDescriptor[$fieldName] += $requestField;
904 return array_filter( $defaultFormDescriptor + $formDescriptor );
909class_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.
array $params
The job parameters.
An error page which can definitely be safely rendered using the OutputPage.
An IContextSource implementation which will inherit context from another source but allow individual ...
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.
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.
getDefaultAction( $subPage)
Get the default action for this special page if none is given via URL/POST data.
hasOwnSubmitButton(AuthenticationRequest $req)
Checks whether the given AuthenticationRequest has its own submit button.
getRequest()
Get the WebRequest being used for this instance.
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.
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.