22 AuthManager::ACTION_LOGIN, AuthManager::ACTION_LOGIN_CONTINUE,
23 AuthManager::ACTION_CREATE, AuthManager::ACTION_CREATE_CONTINUE,
24 AuthManager::ACTION_LINK, AuthManager::ACTION_LINK_CONTINUE,
25 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK,
59 array $requests, array $fieldInfo, array &$formDescriptor, $action
72 return $this->savedRequest ?: $this->
getContext()->getRequest();
85 protected function setRequest( array $data, $wasPosted =
null ) {
87 if ( $wasPosted ===
null ) {
88 $wasPosted = $request->wasPosted();
90 $this->savedRequest =
new DerivativeRequest( $request, $data + $request->getQueryValues(),
124 $authManager = MediaWikiServices::getInstance()->getAuthManager();
125 $key =
'AuthManagerSpecialPage:return:' . $this->
getName();
132 $authData = array_diff_key( $this->
getRequest()->getValues(),
133 $preservedParams, [
'title' => 1 ] );
134 $authManager->setAuthenticationSessionData( $key, $authData );
141 $authData = $authManager->getAuthenticationSessionData( $key );
143 $authManager->removeAuthenticationSessionData( $key );
144 $this->isReturn =
true;
162 $authManager = MediaWikiServices::getInstance()->getAuthManager();
164 $key =
'AuthManagerSpecialPage:reauth:' . $this->
getName();
167 if ( $securityLevel ) {
168 $securityStatus = $authManager->securitySensitiveOperationStatus( $securityLevel );
169 if ( $securityStatus === AuthManager::SEC_REAUTH ) {
170 $queryParams = array_diff_key( $request->getQueryValues(), [
'title' =>
true ] );
172 if ( $request->wasPosted() ) {
175 $key .=
':' . $uniqueId;
177 $queryParams = [
'authUniqueId' => $uniqueId ] + $queryParams;
178 $authData = array_diff_key( $request->getValues(),
179 $this->getPreservedParams(
false ), [
'title' => 1 ] );
180 $authManager->setAuthenticationSessionData( $key, $authData );
184 $url =
$title->getFullURL( [
185 'returnto' => $this->
getFullTitle()->getPrefixedDBkey(),
187 'force' => $securityLevel,
192 } elseif ( $securityStatus !== AuthManager::SEC_OK ) {
193 throw new ErrorPageError(
'cannotauth-not-allowed-title',
'cannotauth-not-allowed' );
197 $uniqueId = $request->getVal(
'authUniqueId' );
199 $key .=
':' . $uniqueId;
200 $authData = $authManager->getAuthenticationSessionData( $key );
202 $authManager->removeAuthenticationSessionData( $key );
226 return array_key_exists( $defaultKey, static::$messages )
227 ? static::$messages[$defaultKey] : $defaultKey;
254 !$reset && $this->subPage ===
$subPage && $this->authAction
262 $this->authAction =
$authAction ?: $request->getText(
'authAction' );
263 if ( !in_array( $this->authAction, static::$allowedActions,
true ) ) {
265 if ( $request->wasPosted() ) {
267 if ( in_array( $continueAction, static::$allowedActions,
true ) ) {
268 $this->authAction = $continueAction;
273 $allReqs = MediaWikiServices::getInstance()->getAuthManager()->getAuthenticationRequests(
274 $this->authAction, $this->
getUser() );
275 $this->authRequests = array_filter( $allReqs,
function ( $req ) {
285 return in_array( $this->authAction, [
286 AuthManager::ACTION_LOGIN_CONTINUE,
287 AuthManager::ACTION_CREATE_CONTINUE,
288 AuthManager::ACTION_LINK_CONTINUE,
299 case AuthManager::ACTION_LOGIN:
300 $action = AuthManager::ACTION_LOGIN_CONTINUE;
302 case AuthManager::ACTION_CREATE:
303 $action = AuthManager::ACTION_CREATE_CONTINUE;
305 case AuthManager::ACTION_LINK:
306 $action = AuthManager::ACTION_LINK_CONTINUE;
321 $authManager = MediaWikiServices::getInstance()->getAuthManager();
322 if ( !in_array( $action, static::$allowedActions,
true ) ) {
323 throw new InvalidArgumentException(
'invalid action: ' . $action );
328 : $authManager->getAuthenticationRequests( $action );
335 case AuthManager::ACTION_LOGIN:
336 case AuthManager::ACTION_LOGIN_CONTINUE:
337 return $authManager->canAuthenticateNow();
338 case AuthManager::ACTION_CREATE:
339 case AuthManager::ACTION_CREATE_CONTINUE:
340 return $authManager->canCreateAccounts();
341 case AuthManager::ACTION_LINK:
342 case AuthManager::ACTION_LINK_CONTINUE:
343 return $authManager->canLinkAccounts();
344 case AuthManager::ACTION_CHANGE:
345 case AuthManager::ACTION_REMOVE:
346 case AuthManager::ACTION_UNLINK:
350 throw new InvalidArgumentException(
'invalid action: ' . $action );
361 if ( !in_array( $action, static::$allowedActions,
true ) ) {
362 throw new InvalidArgumentException(
'invalid action: ' . $action );
365 $authManager = MediaWikiServices::getInstance()->getAuthManager();
370 case AuthManager::ACTION_LOGIN:
371 return $authManager->beginAuthentication( $requests, $returnToUrl );
372 case AuthManager::ACTION_LOGIN_CONTINUE:
373 return $authManager->continueAuthentication( $requests );
374 case AuthManager::ACTION_CREATE:
375 return $authManager->beginAccountCreation( $this->
getUser(), $requests,
377 case AuthManager::ACTION_CREATE_CONTINUE:
378 return $authManager->continueAccountCreation( $requests );
379 case AuthManager::ACTION_LINK:
380 return $authManager->beginAccountLink( $this->
getUser(), $requests, $returnToUrl );
381 case AuthManager::ACTION_LINK_CONTINUE:
382 return $authManager->continueAccountLink( $requests );
383 case AuthManager::ACTION_CHANGE:
384 case AuthManager::ACTION_REMOVE:
385 case AuthManager::ACTION_UNLINK:
386 if ( count( $requests ) > 1 ) {
387 throw new InvalidArgumentException(
'only one auth request can be changed at a time' );
388 } elseif ( !$requests ) {
389 throw new InvalidArgumentException(
'no auth request' );
391 $req = reset( $requests );
392 $status = $authManager->allowsAuthenticationDataChange( $req );
393 $this->
getHookRunner()->onChangeAuthenticationDataAudit( $req, $status );
394 if ( !$status->isGood() ) {
395 return AuthenticationResponse::newFail( $status->getMessage() );
397 $authManager->changeAuthenticationData( $req );
398 return AuthenticationResponse::newPass();
401 throw new InvalidArgumentException(
'invalid action: ' . $action );
418 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
419 $form->setSubmitCallback( [ $this,
'handleFormSubmit' ] );
425 if ( $sessionToken->wasNew() ) {
426 return Status::newFatal( $this->
messageKey(
'authform-newtoken' ) );
427 } elseif ( !$requestTokenValue ) {
428 return Status::newFatal( $this->
messageKey(
'authform-notoken' ) );
429 } elseif ( !$sessionToken->match( $requestTokenValue ) ) {
430 return Status::newFatal( $this->
messageKey(
'authform-wrongtoken' ) );
433 $form->prepareForm();
434 $status = $form->trySubmit();
438 if ( $status ===
true ) {
440 throw new UnexpectedValueException(
'HTMLForm::trySubmit() returned true' );
441 } elseif ( $status ===
false ) {
443 } elseif ( $status instanceof
Status ) {
448 $status = Status::wrap( $status );
449 } elseif ( is_string( $status ) ) {
450 $status = Status::newFatal(
new RawMessage(
'$1', [ $status ] ) );
451 } elseif ( is_array( $status ) ) {
452 if ( is_string( reset( $status ) ) ) {
453 $status = Status::newFatal( ...$status );
454 } elseif ( is_array( reset( $status ) ) ) {
455 $ret = Status::newGood();
456 foreach ( $status as $message ) {
457 $ret->fatal( ...$message );
461 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return value: '
462 .
'first element of array is ' . gettype( reset( $status ) ) );
467 throw new UnexpectedValueException(
'invalid HTMLForm::trySubmit() return type: '
468 . gettype( $status ) );
471 if ( ( !$status || !$status->isOK() ) && $this->isReturn ) {
476 LoggerFactory::getInstance(
'authentication' )
477 ->warning(
'Validation error on return', [
'data' => $form->mFieldData,
478 'status' => $status->getWikiText(
false,
false,
'en' ) ] );
484 AuthManager::ACTION_CHANGE, AuthManager::ACTION_REMOVE, AuthManager::ACTION_UNLINK
486 if ( in_array( $this->authAction, $changeActions,
true ) && $status && !$status->isOK() ) {
487 $this->
getHookRunner()->onChangeAuthenticationDataAudit( reset( $this->authRequests ), $status );
500 $requests = AuthenticationRequest::loadRequestsFromSubmission( $this->authRequests, $data );
504 return Status::newGood( $response );
534 $fieldInfo = AuthenticationRequest::mergeFieldInfo( $requests );
539 return $formDescriptor;
551 if ( $context->getRequest() !== $this->getRequest() ) {
556 $form = HTMLForm::factory(
'ooui', $formDescriptor, $context );
559 $form->addHiddenField(
'authAction', $this->authAction );
571 $status = Status::wrap( $status );
573 $form = $this->
getAuthForm( $this->authRequests, $this->authAction );
574 $form->prepareForm()->displayForm( $status );
589 $customSubmitButtonPresent =
false;
595 foreach ( $requests as $req ) {
596 if ( $req->required === AuthenticationRequest::PRIMARY_REQUIRED ) {
598 $customSubmitButtonPresent =
true;
604 return !$customSubmitButtonPresent;
614 if ( $info[
'type'] ===
'button' ) {
628 foreach ( $formDescriptor as $field => &$definition ) {
630 if ( array_key_exists(
'class', $definition ) ) {
631 $class = $definition[
'class'];
632 } elseif ( array_key_exists(
'type', $definition ) ) {
633 $class = HTMLForm::$typeMappings[$definition[
'type']];
635 if ( $class !== HTMLInfoField::class ) {
636 $definition[
'tabindex'] = $i;
648 return $this->
getRequest()->getSession()->getToken(
'AuthManagerSpecialPage:'
658 return 'wpAuthToken';
671 $formDescriptor = [];
672 foreach ( $fieldInfo as $fieldName => $singleFieldInfo ) {
676 $requestSnapshot =
serialize( $requests );
678 $this->
getHookRunner()->onAuthChangeFormFields( $requests, $fieldInfo,
679 $formDescriptor, $action );
680 if ( $requestSnapshot !==
serialize( $requests ) ) {
681 LoggerFactory::getInstance(
'authentication' )->warning(
682 'AuthChangeFormFields hook changed auth requests' );
689 return $formDescriptor;
704 'name' => $fieldName,
707 if (
$type ===
'submit' && isset( $singleFieldInfo[
'label'] ) ) {
708 $descriptor[
'default'] = $singleFieldInfo[
'label']->plain();
709 } elseif (
$type !==
'submit' ) {
710 $descriptor += array_filter( [
712 'label-message' => self::getField( $singleFieldInfo,
'label' ),
715 if ( isset( $singleFieldInfo[
'options'] ) ) {
716 $descriptor[
'options'] = array_flip( array_map(
function ( $message ) {
718 return $message->parse();
719 }, $singleFieldInfo[
'options'] ) );
722 if ( isset( $singleFieldInfo[
'value'] ) ) {
723 $descriptor[
'default'] = $singleFieldInfo[
'value'];
726 if ( empty( $singleFieldInfo[
'optional'] ) ) {
727 $descriptor[
'required'] =
true;
742 foreach ( $formDescriptor as &$field ) {
743 $field[
'__index'] = $i++;
745 uasort( $formDescriptor,
function ( $first, $second ) {
747 ?: $first[
'__index'] <=> $second[
'__index'];
749 foreach ( $formDescriptor as &$field ) {
750 unset( $field[
'__index'] );
761 protected static function getField( array $array, $fieldName, $default =
null ) {
762 if ( array_key_exists( $fieldName, $array ) ) {
763 return $array[$fieldName];
778 'password' =>
'password',
779 'select' =>
'select',
780 'checkbox' =>
'check',
781 'multiselect' =>
'multiselect',
782 'button' =>
'submit',
783 'hidden' =>
'hidden',
786 if ( !array_key_exists(
$type, $map ) ) {
787 throw new \LogicException(
'invalid field type: ' .
$type );
805 array $fieldInfo, array $formDescriptor, array $defaultFormDescriptor
808 foreach ( $defaultFormDescriptor as $fieldName => $defaultField ) {
812 !isset( $fieldInfo[$fieldName] )
814 !isset( $defaultField[
'baseField'] )
815 || !isset( $fieldInfo[$defaultField[
'baseField']] )
818 !isset( $defaultField[
'type'] )
819 || !in_array( $defaultField[
'type'], [
'submit',
'info' ],
true )
822 $defaultFormDescriptor[$fieldName] =
null;
827 $requestField = $formDescriptor[$fieldName] ?? [];
829 isset( $defaultField[
'label'] )
830 || isset( $defaultField[
'label-message'] )
831 || isset( $defaultField[
'label-raw'] )
833 unset( $requestField[
'label'], $requestField[
'label-message'], $defaultField[
'label-raw'] );
836 $defaultFormDescriptor[$fieldName] += $requestField;
839 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.
getLoginSecurityLevel()
Stable to override.
handleReauthBeforeExecute( $subPage)
Handle redirection when the user needs to (re)authenticate.
beforeExecute( $subPage)
Stable to override.
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)
Stable to override.
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-existend 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.
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...