19 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
20 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
21 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
22 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
55 array $requests, array $fieldInfo, array &$formDescriptor, $action
65 return $this->savedRequest ?: $this->
getContext()->getRequest();
76 protected function setRequest( array $data, $wasPosted =
null ) {
78 if ( $wasPosted ===
null ) {
79 $wasPosted = $request->wasPosted();
81 $this->savedRequest =
new DerivativeRequest( $request, $data + $request->getQueryValues(),
109 $authManager = AuthManager::singleton();
110 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
117 $authData = array_diff_key( $this->
getRequest()->getValues(),
118 $preservedParams, [
'title' => 1 ] );
119 $authManager->setAuthenticationSessionData( $key, $authData );
126 $authData = $authManager->getAuthenticationSessionData( $key );
128 $authManager->removeAuthenticationSessionData( $key );
129 $this->isReturn =
true;
147 $authManager = AuthManager::singleton();
149 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
152 if ( $securityLevel ) {
153 $securityStatus = AuthManager::singleton()
154 ->securitySensitiveOperationStatus( $securityLevel );
155 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
156 $queryParams = array_diff_key( $request->getQueryValues(), [
'title' =>
true ] );
158 if ( $request->wasPosted() ) {
161 $key .=
':' . $uniqueId;
163 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
164 $authData = array_diff_key( $request->getValues(),
165 $this->getPreservedParams(
false ), [
'title' => 1 ] );
166 $authManager->setAuthenticationSessionData( $key, $authData );
170 $url =
$title->getFullURL( [
171 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
173 'force' => $securityLevel,
178 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
179 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
183 $uniqueId = $request->getVal(
'authUniqueId' );
185 $key .=
':' . $uniqueId;
186 $authData = $authManager->getAuthenticationSessionData( $key );
188 $authManager->removeAuthenticationSessionData( $key );
211 return array_key_exists( $defaultKey, static::$messages )
212 ? static::$messages[$defaultKey] : $defaultKey;
237 !$reset && $this->subPage ===
$subPage && $this->authAction
245 $this->authAction =
$authAction ?: $request->getText(
'authAction' );
246 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
248 if ( $request->wasPosted() ) {
250 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
251 $this->authAction = $continueAction;
256 $allReqs = AuthManager::singleton()->getAuthenticationRequests(
257 $this->authAction, $this->
getUser() );
258 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
268 return in_array( $this->authAction, [
269 AuthManager::ACTION_LOGIN_CONTINUE,
270 AuthManager::ACTION_CREATE_CONTINUE,
271 AuthManager::ACTION_LINK_CONTINUE,
282 case AuthManager::ACTION_LOGIN:
283 $action = AuthManager::ACTION_LOGIN_CONTINUE;
285 case AuthManager::ACTION_CREATE:
286 $action = AuthManager::ACTION_CREATE_CONTINUE;
288 case AuthManager::ACTION_LINK:
289 $action = AuthManager::ACTION_LINK_CONTINUE;
304 $authManager = AuthManager::singleton();
305 if ( !in_array( $action, static::$allowedActions,
true ) ) {
306 throw new InvalidArgumentException(
'invalid action: ' . $action );
311 : $authManager->getAuthenticationRequests( $action );
318 case AuthManager::ACTION_LOGIN:
319 case AuthManager::ACTION_LOGIN_CONTINUE:
320 return $authManager->canAuthenticateNow();
321 case AuthManager::ACTION_CREATE:
322 case AuthManager::ACTION_CREATE_CONTINUE:
323 return $authManager->canCreateAccounts();
324 case AuthManager::ACTION_LINK:
325 case AuthManager::ACTION_LINK_CONTINUE:
326 return $authManager->canLinkAccounts();
327 case AuthManager::ACTION_CHANGE:
328 case AuthManager::ACTION_REMOVE:
329 case AuthManager::ACTION_UNLINK:
333 throw new InvalidArgumentException(
'invalid action: ' . $action );
344 if ( !in_array( $action, static::$allowedActions,
true ) ) {
345 throw new InvalidArgumentException(
'invalid action: ' . $action );
348 $authManager = AuthManager::singleton();
353 case AuthManager::ACTION_LOGIN:
354 return $authManager->beginAuthentication( $requests, $returnToUrl );
355 case AuthManager::ACTION_LOGIN_CONTINUE:
356 return $authManager->continueAuthentication( $requests );
357 case AuthManager::ACTION_CREATE:
358 return $authManager->beginAccountCreation( $this->
getUser(), $requests,
360 case AuthManager::ACTION_CREATE_CONTINUE:
361 return $authManager->continueAccountCreation( $requests );
362 case AuthManager::ACTION_LINK:
363 return $authManager->beginAccountLink( $this->
getUser(), $requests, $returnToUrl );
364 case AuthManager::ACTION_LINK_CONTINUE:
365 return $authManager->continueAccountLink( $requests );
366 case AuthManager::ACTION_CHANGE:
367 case AuthManager::ACTION_REMOVE:
368 case AuthManager::ACTION_UNLINK:
369 if ( count( $requests ) > 1 ) {
370 throw new InvalidArgumentException(
'only one auth request can be changed at a time' );
371 } elseif ( !$requests ) {
372 throw new InvalidArgumentException(
'no auth request' );
374 $req = reset( $requests );
375 $status = $authManager->allowsAuthenticationDataChange( $req );
376 Hooks::run(
'ChangeAuthenticationDataAudit', [ $req, $status ] );
377 if ( !$status->isGood() ) {
378 return AuthenticationResponse::newFail( $status->getMessage() );
380 $authManager->changeAuthenticationData( $req );
381 return AuthenticationResponse::newPass();
384 throw new InvalidArgumentException(
'invalid action: ' . $action );
401 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
402 $form->setSubmitCallback( [ $this,
'handleFormSubmit' ] );
408 if ( $sessionToken->wasNew() ) {
409 return Status::newFatal( $this->
messageKey(
'authform-newtoken' ) );
410 } elseif ( !$requestTokenValue ) {
411 return Status::newFatal( $this->
messageKey(
'authform-notoken' ) );
412 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
413 return Status::newFatal( $this->
messageKey(
'authform-wrongtoken' ) );
416 $form->prepareForm();
417 $status = $form->trySubmit();
421 if ( $status ===
true ) {
423 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
424 } elseif ( $status ===
false ) {
426 } elseif ( $status instanceof
Status ) {
431 $status = Status::wrap( $status );
432 } elseif ( is_string( $status ) ) {
433 $status = Status::newFatal(
new RawMessage(
'$1', [ $status ] ) );
434 } elseif ( is_array( $status ) ) {
435 if ( is_string( reset( $status ) ) ) {
436 $status = Status::newFatal( ...$status );
437 } elseif ( is_array( reset( $status ) ) ) {
438 $ret = Status::newGood();
439 foreach ( $status as $message ) {
440 $ret->fatal( ...$message );
444 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
445 .
'first element of array is ' . gettype( reset( $status ) ) );
450 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
451 . gettype( $status ) );
454 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
459 LoggerFactory::getInstance(
'authentication' )
460 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
461 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
467 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
469 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
470 Hooks::run(
'ChangeAuthenticationDataAudit', [ reset( $this->authRequests ), $status ] );
483 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
515 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
520 return $formDescriptor;
531 if (
$context->getRequest() !== $this->getRequest() ) {
536 $form = HTMLForm::factory(
'ooui', $formDescriptor,
$context );
539 $form->addHiddenField(
'authAction', $this->authAction );
551 $status = Status::wrap( $status );
553 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
554 $form->prepareForm()->displayForm( $status );
568 $customSubmitButtonPresent =
false;
574 foreach ( $requests as $req ) {
575 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
577 $customSubmitButtonPresent =
true;
583 return !$customSubmitButtonPresent;
593 if ( $info[
'type'] ===
'button' ) {
607 foreach ( $formDescriptor as $field => &$definition ) {
609 if ( array_key_exists(
'class', $definition ) ) {
610 $class = $definition[
'class'];
611 } elseif ( array_key_exists(
'type', $definition ) ) {
612 $class = HTMLForm::$typeMappings[$definition[
'type']];
614 if ( $class !== HTMLInfoField::class ) {
615 $definition[
'tabindex'] = $i;
626 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
635 return 'wpAuthToken';
648 $formDescriptor = [];
649 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
653 $requestSnapshot =
serialize( $requests );
655 \Hooks::run(
'AuthChangeFormFields', [ $requests, $fieldInfo, &$formDescriptor, $action ] );
656 if ( $requestSnapshot !==
serialize( $requests ) ) {
657 LoggerFactory::getInstance(
'authentication' )->warning(
658 'AuthChangeFormFields hook changed auth requests' );
665 return $formDescriptor;
680 'name' => $fieldName,
683 if (
$type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
684 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
685 } elseif (
$type !==
'submit' ) {
686 $descriptor += array_filter( [
688 'label-message' => self::getField( $singleFieldInfo,
'label' ),
691 if ( isset( $singleFieldInfo[
'options'] ) ) {
692 $descriptor[
'options'] = array_flip( array_map(
function ( $message ) {
694 return $message->parse();
695 }, $singleFieldInfo[
'options'] ) );
698 if ( isset( $singleFieldInfo[
'value'] ) ) {
699 $descriptor[
'default'] = $singleFieldInfo[
'value'];
702 if ( empty( $singleFieldInfo[
'optional'] ) ) {
703 $descriptor[
'required'] =
true;
718 foreach ( $formDescriptor as &$field ) {
719 $field[
'__index'] = $i++;
721 uasort( $formDescriptor,
function ( $first, $second ) {
723 ?: $first[
'__index'] <=> $second[
'__index'];
725 foreach ( $formDescriptor as &$field ) {
726 unset( $field[
'__index'] );
737 protected static function getField( array $array, $fieldName, $default =
null ) {
738 if ( array_key_exists( $fieldName, $array ) ) {
739 return $array[$fieldName];
754 'password' =>
'password',
755 'select' =>
'select',
756 'checkbox' =>
'check',
757 'multiselect' =>
'multiselect',
758 'button' =>
'submit',
759 'hidden' =>
'hidden',
762 if ( !array_key_exists(
$type, $map ) ) {
763 throw new \LogicException(
'invalid field type: ' .
$type );
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.
getLoginSecurityLevel()
Tells if the special page does something security-sensitive and needs extra defense against a stolen ...
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
beforeExecute( $subPage)
Gets called before.
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.
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.
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
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.
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...