Revert "Use CsrfTokenSet as CSRF token source"

This reverts commit 0d75fdb4f7.

Bug: T287542
Change-Id: Iedd3461869f973f8d621a39e6ad4674cbb577551
This commit is contained in:
Kunal Mehta 2021-08-04 23:54:11 -07:00
parent b01945fb4e
commit a85f569dd1
53 changed files with 212 additions and 302 deletions

View file

@ -55,7 +55,6 @@ use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\RevisionStoreRecord;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserNameUtils;
use MediaWiki\Watchlist\WatchlistManager;
@ -1627,7 +1626,9 @@ class EditPage implements IEditObject {
* @internal
*/
public function tokenOk( &$request ) {
$this->mTokenOk = ( new CsrfTokenSet( $request ) )->matchTokenField();
$token = $request->getVal( 'wpEditToken' );
$user = $this->context->getUser();
$this->mTokenOk = $user->matchEditToken( $token );
return $this->mTokenOk;
}
@ -3493,10 +3494,7 @@ class EditPage implements IEditObject {
*/
$this->context->getOutput()->addHTML(
"\n" .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->context->getCsrfTokenSet()->getToken()->toString()
) .
Html::hidden( "wpEditToken", $this->context->getUser()->getEditToken() ) .
"\n"
);
}

View file

@ -25,7 +25,6 @@
use MediaWiki\Linker\LinkRenderer;
use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserOptionsLookup;
use MediaWiki\Watchlist\WatchlistManager;
@ -112,6 +111,7 @@ class FileDeleteForm {
$request = $this->context->getRequest();
$this->oldimage = $request->getText( 'oldimage', '' );
$token = $request->getText( 'wpEditToken' );
# Flag to hide all contents of the archived revisions
$suppress = $request->getCheck( 'wpSuppress' ) &&
$this->context->getAuthority()->isAllowed( 'suppressrevision' );
@ -130,10 +130,7 @@ class FileDeleteForm {
}
// Perform the deletion if appropriate
if ( $request->wasPosted() &&
$this->context->getCsrfTokenSet()
->matchTokenField( CsrfTokenSet::DEFAULT_FIELD_NAME, $this->oldimage )
) {
if ( $request->wasPosted() && $this->context->getUser()->matchEditToken( $token, $this->oldimage ) ) {
$permissionStatus = PermissionStatus::newEmpty();
if ( !$this->context->getAuthority()->authorizeWrite(
'delete', $this->title, $permissionStatus
@ -434,8 +431,8 @@ class FileDeleteForm {
$fieldset,
new OOUI\HtmlSnippet(
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->context->getCsrfTokenSet()->getToken( $this->oldimage )->toString()
'wpEditToken',
$this->context->getUser()->getEditToken( $this->oldimage )
)
)
);

View file

@ -2184,7 +2184,7 @@ class Linker {
$query = [
'action' => 'rollback',
'from' => $revUserText,
'token' => $context->getCsrfTokenSet()->getToken( 'rollback' )->toString(),
'token' => $context->getUser()->getEditToken( 'rollback' ),
];
$attrs = [

View file

@ -3481,7 +3481,7 @@ class OutputPage extends ContextSource {
// Anons have predictable edit tokens
return false;
}
if ( !$this->getCsrfTokenSet()->matchTokenField() ) {
if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
return false;
}

View file

@ -28,7 +28,6 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Watchlist\WatchlistManager;
/**
@ -328,10 +327,11 @@ class ProtectionForm {
return false;
}
if ( !$this->mContext->getCsrfTokenSet()->matchTokenField(
CsrfTokenSet::DEFAULT_FIELD_NAME,
[ 'protect', $this->mTitle->getPrefixedDBkey() ]
) ) {
$token = $this->mRequest->getVal( 'wpEditToken' );
$legacyUser = MediaWikiServices::getInstance()
->getUserFactory()
->newFromAuthority( $this->mPerformer );
if ( !$legacyUser->matchEditToken( $token, [ 'protect', $this->mTitle->getPrefixedDBkey() ] ) ) {
$this->show( [ 'sessionfailure' ] );
return false;
}
@ -585,13 +585,13 @@ class ProtectionForm {
}
if ( !$this->disabled ) {
$fields[CsrfTokenSet::DEFAULT_FIELD_NAME] = [
'name' => CsrfTokenSet::DEFAULT_FIELD_NAME,
$legacyUser = MediaWikiServices::getInstance()
->getUserFactory()
->newFromAuthority( $this->mPerformer );
$fields['wpEditToken'] = [
'name' => 'wpEditToken',
'type' => 'hidden',
'default' => $this->mContext
->getCsrfTokenSet()
->getToken( [ 'protect', $this->mTitle->getPrefixedDBkey() ] )
->toString(),
'default' => $legacyUser->getEditToken( [ 'protect', $this->mTitle->getPrefixedDBkey() ] ),
];
}

View file

@ -175,7 +175,7 @@ abstract class EditHandler extends ActionModuleBasedHandler {
}
// Since the session is safe against CSRF, just use a known-good token.
return $this->getApiMain()->getCsrfTokenSet()->getToken()->toString();
return $this->getUser()->getEditToken();
} else {
return $body['token'] ?? '';
}

View file

@ -153,7 +153,7 @@ class RollbackAction extends FormAction {
] );
}
if ( !$this->getContext()->getCsrfTokenSet()->matchTokenField( 'token', 'rollback' ) ) {
if ( !$user->matchEditToken( $request->getVal( 'token' ), 'rollback' ) ) {
throw new ErrorPageError( 'sessionfailure-title', 'sessionfailure' );
}

View file

@ -23,7 +23,6 @@
use MediaWiki\MediaWikiServices;
use MediaWiki\Page\PageIdentity;
use MediaWiki\Permissions\Authority;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Watchlist\WatchlistManager;
use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
@ -346,9 +345,7 @@ class WatchAction extends FormAction {
$action = 'watch';
}
// This must match ApiWatch and ResourceLoaderUserOptionsModule
return ( new CsrfTokenSet( $user->getRequest() ) )
->getToken( $action )
->toString();
return $user->getEditToken( $action );
}
public function doesWrites() {

View file

@ -1097,7 +1097,11 @@ abstract class ApiBase extends ContextSource {
}
$webUiSalt = $this->getWebUITokenSalt( $params );
if ( $webUiSalt !== null && $this->getCsrfTokenSet()->matchToken( $token, $webUiSalt ) ) {
if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
$token,
$webUiSalt,
$this->getRequest()
) ) {
return true;
}

View file

@ -26,7 +26,6 @@ use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Revision\RevisionLookup;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserOptionsLookup;
use MediaWiki\Watchlist\WatchlistManager;
@ -348,7 +347,7 @@ class ApiEditPage extends ApiBase {
'wpTextbox1' => $params['text'],
'format' => $contentFormat,
'model' => $contentModel,
CsrfTokenSet::DEFAULT_FIELD_NAME => $params['token'],
'wpEditToken' => $params['token'],
'wpIgnoreBlankSummary' => true,
'wpIgnoreBlankArticle' => true,
'wpIgnoreSelfRedirect' => true,

View file

@ -191,7 +191,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
if ( $fld_token ) {
// Undelete tokens are identical for all pages, so we cache one here
$token = $this->getCsrfTokenSet()->getToken()->toString();
$token = $user->getEditToken( '', $this->getMain()->getRequest() );
}
$dir = $params['dir'];

View file

@ -24,7 +24,6 @@ use MediaWiki\Languages\LanguageConverterFactory;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\ParamValidator\TypeDef\TitleDef;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Session\CsrfTokenSet;
/**
* A query module to show basic page information.
@ -209,9 +208,7 @@ class ApiQueryInfo extends ApiQueryBase {
// The token is always the same, let's exploit that
if ( !isset( self::$cachedTokens['edit'] ) ) {
self::$cachedTokens['edit'] = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken()
->toString();
self::$cachedTokens['edit'] = $user->getEditToken();
}
return self::$cachedTokens['edit'];
@ -284,9 +281,7 @@ class ApiQueryInfo extends ApiQueryBase {
// The token is always the same, let's exploit that
if ( !isset( self::$cachedTokens['email'] ) ) {
self::$cachedTokens['email'] = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken()
->toString();
self::$cachedTokens['email'] = $user->getEditToken();
}
return self::$cachedTokens['email'];
@ -304,9 +299,7 @@ class ApiQueryInfo extends ApiQueryBase {
// The token is always the same, let's exploit that
if ( !isset( self::$cachedTokens['import'] ) ) {
self::$cachedTokens['import'] = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken()
->toString();
self::$cachedTokens['import'] = $user->getEditToken();
}
return self::$cachedTokens['import'];
@ -324,9 +317,7 @@ class ApiQueryInfo extends ApiQueryBase {
// The token is always the same, let's exploit that
if ( !isset( self::$cachedTokens['watch'] ) ) {
self::$cachedTokens['watch'] = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken( 'watch' )
->toString();
self::$cachedTokens['watch'] = $user->getEditToken( 'watch' );
}
return self::$cachedTokens['watch'];
@ -344,9 +335,7 @@ class ApiQueryInfo extends ApiQueryBase {
// The token is always the same, let's exploit that
if ( !isset( self::$cachedTokens['options'] ) ) {
self::$cachedTokens['options'] = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken()
->toString();
self::$cachedTokens['options'] = $user->getEditToken();
}
return self::$cachedTokens['options'];

View file

@ -23,7 +23,6 @@
use MediaWiki\ParamValidator\TypeDef\UserDef;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Storage\NameTableAccessException;
use MediaWiki\Storage\NameTableStore;
@ -127,8 +126,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
static $cachedPatrolToken = null;
if ( $cachedPatrolToken === null ) {
$cachedPatrolToken = ( new CsrfTokenSet( $user->getRequest() ) )
->getToken( 'patrol' )->toString();
$cachedPatrolToken = $user->getEditToken( 'patrol' );
}
return $cachedPatrolToken;

View file

@ -25,7 +25,6 @@ use MediaWiki\ParamValidator\TypeDef\UserDef;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\SlotRoleRegistry;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Storage\NameTableAccessException;
use MediaWiki\Storage\NameTableStore;
@ -120,8 +119,7 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
return false;
}
return ( new CsrfTokenSet( $user->getRequest() ) )
->getToken( 'rollback' )->toString();
return $user->getEditToken( 'rollback' );
}
protected function run( ApiPageSet $resultPageSet = null ) {

View file

@ -219,7 +219,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
!$this->lacksSameOriginSecurity() &&
$this->getAuthority()->isAllowed( 'editmyoptions' )
) {
$vals['preferencestoken'] = $this->getCsrfTokenSet()->getToken()->toString();
$vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() );
}
if ( isset( $this->prop['editcount'] ) ) {

View file

@ -22,7 +22,6 @@
use MediaWiki\Auth\AuthManager;
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserFactory;
use MediaWiki\User\UserGroupManager;
use MediaWiki\User\UserNameUtils;
@ -134,9 +133,7 @@ class ApiQueryUsers extends ApiQueryBase {
public static function getUserrightsToken( User $actingUser, $targetUser ) {
// Since the permissions check for userrights is non-trivial,
// don't bother with it here
return ( new CsrfTokenSet( $actingUser->getRequest() ) )
->getToken( $targetUser->getName() )
->toString();
return $actingUser->getEditToken( $targetUser->getName() );
}
public function execute() {

View file

@ -20,7 +20,6 @@
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Permissions\Authority;
use MediaWiki\Session\CsrfTokenSet;
/**
* An IContextSource implementation which will inherit context from another source
@ -288,14 +287,4 @@ class DerivativeContext extends ContextSource implements MutableContext {
// phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
return wfMessage( $key, ...$params )->setContext( $this );
}
/**
* Get a repository to obtain and match CSRF tokens.
*
* @return CsrfTokenSet
* @since 1.37
*/
public function getCsrfTokenSet(): CsrfTokenSet {
return new CsrfTokenSet( $this->getRequest() );
}
}

View file

@ -24,7 +24,6 @@
use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\PageReference;
use MediaWiki\Session\CsrfTokenSet;
/**
* Object handling generic submission, CSRF protection, layout and
@ -593,12 +592,12 @@ class HTMLForm extends ContextSource {
if ( $this->getMethod() !== 'post' ) {
$tokenOkay = true; // no session check needed
} elseif ( $this->getRequest()->wasPosted() ) {
$editToken = $this->getRequest()->getVal( CsrfTokenSet::DEFAULT_FIELD_NAME );
$editToken = $this->getRequest()->getVal( 'wpEditToken' );
if ( $this->getUser()->isRegistered() || $editToken !== null ) {
// Session tokens for logged-out users have no security value.
// However, if the user gave one, check it in order to give a nice
// "session expired" error instead of "permission denied" or such.
$tokenOkay = $this->getCsrfTokenSet()->matchToken( $editToken, $this->mTokenSalt );
$tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
} else {
$tokenOkay = true;
}
@ -1195,9 +1194,9 @@ class HTMLForm extends ContextSource {
}
if ( $this->getMethod() === 'post' ) {
$html .= Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getCsrfTokenSet()->getToken( $this->mTokenSalt )->toString(),
[ 'id' => CsrfTokenSet::DEFAULT_FIELD_NAME ]
'wpEditToken',
$this->getUser()->getEditToken( $this->mTokenSalt ),
[ 'id' => 'wpEditToken' ]
) . "\n";
$html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
}

View file

@ -1,7 +1,5 @@
<?php
use MediaWiki\Session\CsrfTokenSet;
/**
* The parent class to generate form fields. Any field type should
* be a subclass of this.
@ -378,8 +376,7 @@ abstract class HTMLFormField {
* @return bool
*/
protected function isSubmitAttempt( WebRequest $request ) {
return $request->getCheck( CsrfTokenSet::DEFAULT_FIELD_NAME ) ||
$request->getCheck( 'wpFormIdentifier' );
return $request->getCheck( 'wpEditToken' ) || $request->getCheck( 'wpFormIdentifier' );
}
/**

View file

@ -1,7 +1,5 @@
<?php
use MediaWiki\Session\CsrfTokenSet;
/**
* A container for HTMLFormFields that allows for multiple copies of the set of
* fields to be displayed to and entered by the user.
@ -146,9 +144,7 @@ class HTMLFormFieldCloner extends HTMLFormField {
public function loadDataFromRequest( $request ) {
// It's possible that this might be posted with no fields. Detect that
// by looking for an edit token.
if ( !$request->getCheck( CsrfTokenSet::DEFAULT_FIELD_NAME ) &&
$request->getArray( $this->mName ) === null
) {
if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
return $this->getDefault();
}
@ -164,7 +160,8 @@ class HTMLFormFieldCloner extends HTMLFormField {
continue;
}
// Add back in $request->getValues() so things that look for e.g. wpEditToken don't fail.
// Add back in $request->getValues() so things that look for e.g.
// wpEditToken don't fail.
$data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
$fields = $this->createFieldsForKey( $key );

View file

@ -30,7 +30,6 @@ use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserIdentity;
use MediaWiki\User\UserNameUtils;
use MediaWiki\Watchlist\WatchlistManager;
@ -1897,11 +1896,8 @@ class Article implements Page {
$reason = $deleteReasonList;
}
if ( $request->wasPosted() &&
$this->getContext()->getCsrfTokenSet()->matchTokenField(
CsrfTokenSet::DEFAULT_FIELD_NAME,
[ 'delete', $this->getTitle()->getPrefixedText() ]
)
if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
[ 'delete', $this->getTitle()->getPrefixedText() ] )
) {
# Flag to hide all contents of the archived revisions
@ -2130,13 +2126,7 @@ class Article implements Page {
$form->appendContent(
$fieldset,
new OOUI\HtmlSnippet(
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()
->getCsrfTokenSet()
->getToken( [ 'delete', $title->getPrefixedText() ] )
->toString()
)
Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
)
);

View file

@ -214,7 +214,7 @@ class ImageHistoryList extends ContextSource {
[
'target' => $this->title->getPrefixedText(),
'file' => $img,
'token' => $this->getCsrfTokenSet()->getToken( $img )->toString(),
'token' => $user->getEditToken( $img )
]
);
} else {

View file

@ -50,15 +50,15 @@ class ResourceLoaderUserOptionsModule extends ResourceLoaderModule {
* @return string JavaScript code
*/
public function getScript( ResourceLoaderContext $context ) {
$tokenSet = RequestContext::getMain()->getCsrfTokenSet();
$user = $context->getUserObj();
$tokens = [
'patrolToken' => $tokenSet->getToken( 'patrol' )->toString(),
'watchToken' => $tokenSet->getToken( 'watch' )->toString(),
'csrfToken' => $tokenSet->getToken()->toString(),
'patrolToken' => $user->getEditToken( 'patrol' ),
'watchToken' => $user->getEditToken( 'watch' ),
'csrfToken' => $user->getEditToken(),
];
$script = 'mw.user.tokens.set(' . $context->encodeJson( $tokens ) . ');';
$user = $context->getUserObj();
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup();
$options = $userOptionsLookup->getOptions( $user, UserOptionsLookup::EXCLUDE_DEFAULTS );
// Optimisation: Only output this function call if the user has non-default settings.

View file

@ -94,7 +94,7 @@ class RevDelArchivedFileItem extends RevDelFileItem {
[
'target' => $this->list->getPageName(),
'file' => $key,
'token' => $this->list->getCsrfTokenSet()->getToken( $key )->toString(),
'token' => $this->list->getUser()->getEditToken( $key )
]
);
}
@ -124,7 +124,7 @@ class RevDelArchivedFileItem extends RevDelFileItem {
[
'target' => $this->list->getPageName(),
'file' => $file->getKey(),
'token' => $this->list->getCsrfTokenSet()->getToken( $file->getKey() )->toString(),
'token' => $user->getEditToken( $file->getKey() )
]
),
];

View file

@ -154,9 +154,8 @@ class RevDelFileItem extends RevDelItem {
[
'target' => $this->list->getPageName(),
'file' => $this->file->getArchiveName(),
'token' => $this->list->getCsrfTokenSet()
->getToken( $this->file->getArchiveName() )
->toString(),
'token' => $this->list->getUser()->getEditToken(
$this->file->getArchiveName() )
]
);
}
@ -236,9 +235,7 @@ class RevDelFileItem extends RevDelItem {
[
'target' => $this->list->getPageName(),
'file' => $file->getArchiveName(),
'token' => $this->list->getCsrfTokenSet()
->getToken( $file->getArchiveName() )
->toString(),
'token' => $user->getEditToken( $file->getArchiveName() )
]
),
];

View file

@ -326,13 +326,10 @@ class SearchFormWidget {
false,
// The token goes here rather than in a hidden field so it
// is only sent when necessary (not every form submission)
[
'value' => $this->specialSearch
->getContext()
->getCsrfTokenSet()
->getToken( 'searchnamespace' )
->toString(),
]
[ 'value' => $user->getEditToken(
'searchnamespace',
$this->specialSearch->getRequest()
) ]
);
}

View file

@ -20,7 +20,6 @@
*/
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Session\CsrfTokenSet;
/**
* Special page for adding and removing change tags to individual revisions.
@ -289,13 +288,7 @@ class SpecialEditTags extends UnlistedSpecialPage {
'</td>' .
"</tr>\n" .
Xml::closeElement( 'table' ) .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()
->getCsrfTokenSet()
->getToken()
->toString()
) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
@ -407,7 +400,8 @@ class SpecialEditTags extends UnlistedSpecialPage {
protected function submit() {
// Check edit token on submission
$request = $this->getRequest();
if ( $this->submitClicked && !$this->getContext()->getCsrfTokenSet()->matchTokenField() ) {
$token = $request->getVal( 'wpEditToken' );
if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
return false;
}

View file

@ -23,7 +23,6 @@
use MediaWiki\MediaWikiServices;
use MediaWiki\Preferences\MultiUsernameFilter;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserNamePrefixSearch;
use MediaWiki\User\UserNameUtils;
use MediaWiki\User\UserOptionsLookup;
@ -147,7 +146,7 @@ class SpecialEmailUser extends UnlistedSpecialPage {
// error out if sending user cannot do this
$error = self::getPermissionsError(
$this->getUser(),
$this->getRequest()->getVal( CsrfTokenSet::DEFAULT_FIELD_NAME ),
$this->getRequest()->getVal( 'wpEditToken' ),
$this->getConfig()
);

View file

@ -293,7 +293,7 @@ class SpecialExpandTemplates extends SpecialPage {
// do not show the preview unless anonymous editing is allowed.
if ( $user->isAnon() && !$this->getAuthority()->isAllowed( 'edit' ) ) {
$error = [ 'expand_templates_preview_fail_html_anon' ];
} elseif ( !$this->getContext()->getCsrfTokenSet()->matchTokenField() ) {
} elseif ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ), '', $request ) ) {
$error = [ 'expand_templates_preview_fail_html' ];
} else {
$error = false;

View file

@ -139,7 +139,7 @@ class SpecialImport extends SpecialPage {
$user = $this->getUser();
$fullInterwikiPrefix = null;
if ( !$this->getContext()->getCsrfTokenSet()->matchTokenField() ) {
if ( !$user->matchEditToken( $request->getVal( 'wpEditToken' ) ) ) {
$source = Status::newFatal( 'import-token-mismatch' );
} elseif ( $sourceName === 'upload' ) {
$isUpload = true;

View file

@ -25,7 +25,6 @@ use MediaWiki\Cache\LinkBatchFactory;
use MediaWiki\Page\MergeHistoryFactory;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Session\CsrfTokenSet;
use Wikimedia\Rdbms\ILoadBalancer;
/**
@ -125,7 +124,7 @@ class SpecialMergeHistory extends SpecialPage {
$this->mComment = $request->getText( 'wpComment' );
$this->mMerge = $request->wasPosted()
&& $this->getContext()->getCsrfTokenSet()->matchTokenField();
&& $this->getUser()->matchEditToken( $request->getVal( 'wpEditToken' ) );
// target page
if ( $this->mSubmitted ) {
@ -307,10 +306,7 @@ class SpecialMergeHistory extends SpecialPage {
$misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() );
$misc .= Html::hidden( 'target', $this->mTarget );
$misc .= Html::hidden( 'dest', $this->mDest );
$misc .= Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken()->toString()
);
$misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
$misc .= Xml::closeElement( 'form' );
$out->addHTML( $misc );

View file

@ -27,7 +27,6 @@ use MediaWiki\MediaWikiServices;
use MediaWiki\Page\MovePageFactory;
use MediaWiki\Page\WikiPageFactory;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserOptionsLookup;
use MediaWiki\Watchlist\WatchlistManager;
use Wikimedia\Rdbms\ILoadBalancer;
@ -207,7 +206,7 @@ class MovePageForm extends UnlistedSpecialPage {
$this->watch = $request->getCheck( 'wpWatch' ) && $user->isRegistered();
if ( $request->getVal( 'action' ) == 'submit' && $request->wasPosted()
&& $this->getContext()->getCsrfTokenSet()->matchTokenField()
&& $user->matchEditToken( $request->getVal( 'wpEditToken' ) )
) {
$this->doSubmit();
} else {
@ -597,10 +596,7 @@ class MovePageForm extends UnlistedSpecialPage {
new OOUI\HtmlSnippet(
$hiddenFields .
Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken()->toString()
)
Html::hidden( 'wpEditToken', $user->getEditToken() )
)
);

View file

@ -23,7 +23,6 @@
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Session\CsrfTokenSet;
/**
* Special page allowing users with the appropriate permissions to view
@ -44,6 +43,9 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
/** @var string Archive name, for reviewing deleted files */
private $archiveName;
/** @var string Edit token for securing image views against XSS */
private $token;
/** @var Title Title object for target parameter */
private $targetObj;
@ -159,6 +161,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
# For reviewing deleted files...
$this->archiveName = $request->getVal( 'file' );
$this->token = $request->getVal( 'token' );
if ( $this->archiveName && $this->targetObj ) {
$this->tryShowFile( $this->archiveName );
@ -340,7 +343,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
throw new PermissionsError( 'deletedtext' );
}
}
if ( !$this->getContext()->getCsrfTokenSet()->matchTokenField( 'token', $archiveName ) ) {
if ( !$user->matchEditToken( $this->token, $archiveName ) ) {
$lang = $this->getLanguage();
$this->getOutput()->addWikiMsg( 'revdelete-show-file-confirm',
$this->targetObj->getText(),
@ -352,10 +355,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
'action' => $this->getPageTitle()->getLocalURL( [
'target' => $this->targetObj->getPrefixedDBkey(),
'file' => $archiveName,
'token' => $this->getContext()
->getCsrfTokenSet()
->getToken( $archiveName )
->toString(),
'token' => $user->getEditToken( $archiveName ),
] )
]
) .
@ -494,10 +494,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
'</td>' .
"</tr>\n" .
Xml::closeElement( 'table' ) .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken()->toString()
) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ) .
Html::hidden( 'target', $this->targetObj->getPrefixedText() ) .
Html::hidden( 'type', $this->typeName ) .
Html::hidden( 'ids', implode( ',', $this->ids ) ) .
@ -627,7 +624,8 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
*/
protected function submit() {
# Check edit token on submission
if ( $this->submitClicked && !$this->getContext()->getCsrfTokenSet()->matchTokenField() ) {
$token = $this->getRequest()->getVal( 'wpEditToken' );
if ( $this->submitClicked && !$this->getUser()->matchEditToken( $token ) ) {
$this->getOutput()->addWikiMsg( 'sessionfailure' );
return false;

View file

@ -730,8 +730,11 @@ class SpecialSearch extends SpecialPage {
$request = $this->getRequest();
if ( $user->isRegistered() &&
$this->getContext()->getCsrfTokenSet()->matchTokenField( 'nsRemember', 'searchnamespace' ) &&
!$this->readOnlyMode->isReadOnly()
$user->matchEditToken(
$request->getVal( 'nsRemember' ),
'searchnamespace',
$request
) && !$this->readOnlyMode->isReadOnly()
) {
// Reset namespace preferences: namespaces are not searched
// when they're not mentioned in the URL parameters.

View file

@ -21,8 +21,6 @@
* @ingroup SpecialPage
*/
use MediaWiki\Session\CsrfTokenSet;
/**
* A special page that lists tags for edits
*
@ -316,7 +314,7 @@ class SpecialTags extends SpecialPage {
// fool HTMLForm into thinking the form hasn't been submitted yet. Otherwise
// we get into an infinite loop!
$context->getRequest()->unsetVal( CsrfTokenSet::DEFAULT_FIELD_NAME );
$context->getRequest()->unsetVal( 'wpEditToken' );
$headerText = $this->msg( 'tags-create-warnings-above', $tag,
count( $status->getWarningsArray() ) )->parseAsBlock() .

View file

@ -29,7 +29,6 @@ use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Revision\RevisionRenderer;
use MediaWiki\Revision\RevisionStore;
use MediaWiki\Revision\SlotRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Storage\NameTableAccessException;
use MediaWiki\Storage\NameTableStore;
use MediaWiki\User\UserOptionsLookup;
@ -172,7 +171,8 @@ class SpecialUndelete extends SpecialPage {
$this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
$this->mFilename = $request->getVal( 'file' );
$posted = $request->wasPosted() && $this->getContext()->getCsrfTokenSet()->matchTokenField();
$posted = $request->wasPosted() &&
$user->matchEditToken( $request->getVal( 'wpEditToken' ) );
$this->mRestore = $request->getCheck( 'restore' ) && $posted;
$this->mRevdel = $request->getCheck( 'revdel' ) && $posted;
$this->mInvert = $request->getCheck( 'invert' ) && $posted;
@ -314,7 +314,7 @@ class SpecialUndelete extends SpecialPage {
} else {
throw new PermissionsError( 'deletedtext' );
}
} elseif ( !$this->getContext()->getCsrfTokenSet()->matchToken( $this->mToken, $this->mFilename ) ) {
} elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
$this->showFileConfirmationForm( $this->mFilename );
} else {
$this->showFile( $this->mFilename );
@ -666,8 +666,8 @@ class SpecialUndelete extends SpecialPage {
'value' => $timestamp ] ) .
Xml::element( 'input', [
'type' => 'hidden',
'name' => CsrfTokenSet::DEFAULT_FIELD_NAME,
'value' => $this->getContext()->getCsrfTokenSet()->getToken()->toString() ] ) .
'name' => 'wpEditToken',
'value' => $user->getEditToken() ] ) .
new OOUI\FieldLayout(
new OOUI\Widget( [
'content' => new OOUI\HorizontalLayout( [
@ -809,7 +809,7 @@ class SpecialUndelete extends SpecialPage {
'action' => $this->getPageTitle()->getLocalURL( [
'target' => $this->mTarget,
'file' => $key,
'token' => $this->getContext()->getCsrfTokenSet()->getToken()->toString(),
'token' => $user->getEditToken( $key ),
] ),
]
) .
@ -988,9 +988,7 @@ class SpecialUndelete extends SpecialPage {
] ),
new OOUI\HtmlSnippet(
Html::hidden( 'target', $this->mTarget ) .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken()->toString() )
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() )
)
);
}
@ -1041,10 +1039,7 @@ class SpecialUndelete extends SpecialPage {
if ( $this->mAllowed ) {
# Slip in the hidden controls here
$misc = Html::hidden( 'target', $this->mTarget );
$misc .= Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken()->toString()
);
$misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
$history .= $misc;
$form->appendContent( new OOUI\HtmlSnippet( $history ) );
@ -1256,7 +1251,7 @@ class SpecialUndelete extends SpecialPage {
[
'target' => $this->mTargetObj->getPrefixedText(),
'file' => $key,
'token' => $this->getContext()->getCsrfTokenSet()->getToken( $key )->toString(),
'token' => $user->getEditToken( $key )
]
);

View file

@ -156,7 +156,8 @@ class SpecialUpload extends SpecialPage {
|| $request->getCheck( 'wpReUpload' ); // b/w compat
// If it was posted check for the token (no remote POST'ing with user credentials)
$this->mTokenOk = $this->getContext()->getCsrfTokenSet()->matchTokenField();
$token = $request->getVal( 'wpEditToken' );
$this->mTokenOk = $this->getUser()->matchEditToken( $token );
$this->uploadFormTextTop = '';
$this->uploadFormTextAfterSummary = '';

View file

@ -22,7 +22,6 @@
*/
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\User\UserGroupManager;
use MediaWiki\User\UserGroupManagerFactory;
use MediaWiki\User\UserIdentity;
@ -188,10 +187,7 @@ class UserrightsPage extends SpecialPage {
$request->wasPosted() &&
$request->getCheck( 'saveusergroups' ) &&
$this->mTarget !== null &&
$this->getContext()->getCsrfTokenSet()->matchTokenField(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->mTarget
)
$user->matchEditToken( $request->getVal( 'wpEditToken' ), $this->mTarget )
) {
/*
* If the user is blocked and they only have "partial" access
@ -775,10 +771,7 @@ class UserrightsPage extends SpecialPage {
]
) .
Html::hidden( 'user', $this->mTarget ) .
Html::hidden(
CsrfTokenSet::DEFAULT_FIELD_NAME,
$this->getContext()->getCsrfTokenSet()->getToken( $this->mTarget )->toString()
) .
Html::hidden( 'wpEditToken', $this->getUser()->getEditToken( $this->mTarget ) ) .
Html::hidden(
'conflictcheck-originalgroups',
implode( ',', $user->getGroups() )

View file

@ -117,7 +117,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
if ( ( $config->get( 'EnotifWatchlist' ) || $config->get( 'ShowUpdatedMarker' ) )
&& $request->getVal( 'reset' )
&& $request->wasPosted()
&& $this->getContext()->getCsrfTokenSet()->matchTokenField( 'token' )
&& $user->matchEditToken( $request->getVal( 'token' ) )
) {
$this->watchlistManager->clearAllUserNotifications( $user );
$output->redirect( $this->getPageTitle()->getFullURL( $opts->getChangedValues() ) );
@ -863,7 +863,7 @@ class SpecialWatchlist extends ChangesListSpecialPage {
'id' => 'mw-watchlist-resetbutton' ] ) . "\n" .
Xml::submitButton( $this->msg( 'enotif_reset' )->text(),
[ 'name' => 'mw-watchlist-reset-submit' ] ) . "\n" .
Html::hidden( 'token', $this->getContext()->getCsrfTokenSet()->getToken()->toString() ) . "\n" .
Html::hidden( 'token', $user->getEditToken() ) . "\n" .
Html::hidden( 'reset', 'all' ) . "\n";
foreach ( $nondefaults as $key => $value ) {
$form .= Html::hidden( $key, $value ) . "\n";

View file

@ -2,7 +2,6 @@
use MediaWiki\EditPage\SpamChecker;
use MediaWiki\Permissions\PermissionManager;
use MediaWiki\Session\CsrfTokenSet;
/**
* Integration tests for the various edit constraints, ensuring
@ -103,6 +102,14 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
);
}
if ( !isset( $edit['wpEditToken'] ) ) {
$edit['wpEditToken'] = $user->getEditToken();
}
if ( !isset( $edit['wpEdittime'] ) && !isset( $edit['editRevId'] ) ) {
$edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
}
if ( !isset( $edit['wpStarttime'] ) ) {
$edit['wpStarttime'] = wfTimestampNow();
}
@ -112,12 +119,7 @@ class EditPageConstraintsTest extends MediaWikiLangTestCase {
}
$req = new FauxRequest( $edit, true ); // session ??
if ( !isset( $edit['wpEditToken'] ) ) {
$req->setVal(
'wpEditToken',
( new CsrfTokenSet( $req ) )->getToken()->toString()
);
}
$context = new RequestContext();
$context->setRequest( $req );
$context->setTitle( $title );

View file

@ -2,7 +2,6 @@
use MediaWiki\MediaWikiServices;
use MediaWiki\Revision\RevisionRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Storage\EditResult;
use MediaWiki\User\UserIdentity;
use Wikimedia\TestingAccessWrapper;
@ -158,6 +157,10 @@ class EditPageTest extends MediaWikiLangTestCase {
$this->assertEditedTextEquals( $baseText, $currentText );
}
if ( !isset( $edit['wpEditToken'] ) ) {
$edit['wpEditToken'] = $user->getEditToken();
}
if ( !isset( $edit['wpEdittime'] ) && !isset( $edit['editRevId'] ) ) {
$edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
}
@ -171,12 +174,6 @@ class EditPageTest extends MediaWikiLangTestCase {
}
$req = new FauxRequest( $edit, true ); // session ??
if ( !isset( $edit['wpEditToken'] ) ) {
$req->setVal(
'wpEditToken',
( new CsrfTokenSet( $req ) )->getToken()->toString()
);
}
$context = new RequestContext();
$context->setRequest( $req );
@ -784,8 +781,11 @@ hello
* @covers EditPage
*/
public function testCheckDirectEditingDisallowed_forNonTextContent() {
$user = $this->getTestUser()->getUser();
$edit = [
'wpTextbox1' => serialize( 'non-text content' ),
'wpEditToken' => $user->getEditToken(),
'wpEdittime' => '',
'editRevId' => 0,
'wpStarttime' => wfTimestampNow(),
@ -810,8 +810,11 @@ hello
}
} );
$user = $this->getTestUser()->getUser();
$status = $this->doEditDummyNonTextPage( [
'wpTextbox1' => 'some text',
'wpEditToken' => $user->getEditToken(),
'wpEdittime' => '',
'editRevId' => 0,
'wpStarttime' => wfTimestampNow(),
@ -834,8 +837,11 @@ hello
}
} );
$user = $this->getTestUser()->getUser();
$status = $this->doEditDummyNonTextPage( [
'wpTextbox1' => 'some text',
'wpEditToken' => $user->getEditToken(),
'wpEdittime' => '',
'editRevId' => 0,
'wpStarttime' => wfTimestampNow(),
@ -857,10 +863,6 @@ hello
$ep->setContextTitle( $title );
$req = new FauxRequest( $edit, true );
$req->setVal(
'wpEditToken',
( new CsrfTokenSet( $req ) )->getToken()->toString()
);
$ep->importFormData( $req );
return $ep->internalAttemptSave( $result, false );

View file

@ -8,6 +8,7 @@ use MediaWiki\Page\PageReferenceValue;
use MediaWiki\Page\PageStoreRecord;
use MediaWiki\Permissions\Authority;
use MediaWiki\Tests\Unit\Permissions\MockAuthorityTrait;
use PHPUnit\Framework\MockObject\MockObject;
use Wikimedia\DependencyStore\KeyValueDependencyStore;
use Wikimedia\TestingAccessWrapper;
@ -3205,40 +3206,64 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
$this->assertArraySubmapSame( $expectedEditableConfig, $op->getJSVars() );
}
/**
* @param bool $registered
* @param bool $matchToken
* @return MockObject|User
*/
private function mockUser( bool $registered, bool $matchToken ) {
$user = $this->createNoOpMock( User::class, [ 'isRegistered', 'matchEditToken' ] );
$user->method( 'isRegistered' )->willReturn( $registered );
$user->method( 'matchEditToken' )->willReturn( $matchToken );
return $user;
}
public function provideUserCanPreview() {
yield 'all good' => [
'performer' => $this->mockRegisteredAuthorityWithPermissions( [ 'edit' ] ),
'matchToken' => true,
'performer' => $this->mockUserAuthorityWithPermissions(
$this->mockUser( true, true ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'submit' ], true ),
true
];
yield 'get request' => [
'performer' => $this->mockRegisteredAuthorityWithPermissions( [ 'edit' ] ),
'matchToken' => true,
'performer' => $this->mockUserAuthorityWithPermissions(
$this->mockUser( true, true ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'submit' ], false ),
false
];
yield 'not a submit action' => [
'performer' => $this->mockRegisteredAuthorityWithPermissions( [ 'edit' ] ),
'matchToken' => true,
'performer' => $this->mockUserAuthorityWithPermissions(
$this->mockUser( true, true ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'something' ], true ),
false
];
yield 'anon can not' => [
'performer' => $this->mockAnonAuthorityWithPermissions( [ 'edit' ] ),
'matchToken' => true,
'performer' => $this->mockUserAuthorityWithPermissions(
$this->mockUser( false, true ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'submit' ], true ),
false
];
yield 'token not match' => [
'performer' => $this->mockRegisteredAuthorityWithPermissions( [ 'edit' ] ),
'matchToken' => false,
'performer' => $this->mockUserAuthorityWithPermissions(
$this->mockUser( true, false ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'submit' ], true ),
false
];
yield 'no permission' => [
'performer' => $this->mockRegisteredAuthorityWithoutPermissions( [ 'edit' ] ),
'matchToken' => true,
'performer' => $this->mockUserAuthorityWithoutPermissions(
$this->mockUser( true, true ),
[ 'edit' ]
),
'request' => new FauxRequest( [ 'action' => 'submit' ], true ),
false
];
@ -3248,9 +3273,7 @@ class OutputPageTest extends MediaWikiIntegrationTestCase {
* @dataProvider provideUserCanPreview
* @covers OutputPage::userCanPreview
*/
public function testUserCanPreview( Authority $performer, bool $matchToken, WebRequest $request, bool $expected ) {
// We don't set the user in the session, so we logged out token should be good enough.
$request->setVal( 'wpEditToken', $matchToken ? new LoggedOutEditToken() : 'invalid' );
public function testUserCanPreview( Authority $performer, WebRequest $request, bool $expected ) {
$op = $this->newInstance( [], $request, null, $performer );
$this->assertSame( $expected, $op->userCanPreview() );
}

View file

@ -6,7 +6,6 @@ use Article;
use DerivativeContext;
use ErrorPageError;
use FauxRequest;
use MediaWiki\Session\CsrfTokenSet;
use MediaWikiIntegrationTestCase;
use RequestContext;
use RollbackAction;
@ -44,13 +43,6 @@ class RollbackActionTest extends MediaWikiIntegrationTestCase {
private function getRollbackAction( WebRequest $request ) {
$context = new DerivativeContext( RequestContext::getMain() );
$context->setTitle( $this->testPage );
$request->getSession()->setUser( $this->sysop );
if ( !$request->getVal( 'token' ) ) {
$request->setVal(
'token',
( new CsrfTokenSet( $request ) )->getToken( 'rollback' )->toString()
);
}
$context->setRequest( $request );
$context->setUser( $this->sysop );
$mwServices = $this->getServiceContainer();
@ -110,6 +102,7 @@ class RollbackActionTest extends MediaWikiIntegrationTestCase {
public function testRollback() {
$request = new FauxRequest( [
'from' => $this->vandal->getName(),
'token' => $this->sysop->getEditToken( 'rollback' ),
] );
$rollbackAction = $this->getRollbackAction( $request );
$rollbackAction->handleRollbackRequest();
@ -133,6 +126,7 @@ class RollbackActionTest extends MediaWikiIntegrationTestCase {
public function testRollbackMarkBot() {
$request = new FauxRequest( [
'from' => $this->vandal->getName(),
'token' => $this->sysop->getEditToken( 'rollback' ),
'bot' => true,
] );
$rollbackAction = $this->getRollbackAction( $request );

View file

@ -453,8 +453,8 @@ class WatchActionTest extends MediaWikiIntegrationTestCase {
$this->hideDeprecated( 'WatchAction::getWatchToken' );
$user = $this->createMock( User::class );
$user->expects( $this->once() )
->method( 'getRequest' )
->willReturn( new FauxRequest() );
->method( 'getEditToken' )
->with( 'watch' );
WatchAction::getWatchToken( $this->watchAction->getTitle(), $user, 'INVALID_ACTION' );
}
@ -466,9 +466,7 @@ class WatchActionTest extends MediaWikiIntegrationTestCase {
public function testGetWatchTokenProxiesUserGetEditToken() {
$this->hideDeprecated( 'WatchAction::getWatchToken' );
$user = $this->createMock( User::class );
$user->expects( $this->once() )
->method( 'getRequest' )
->willReturn( new FauxRequest() );
$user->expects( $this->once() )->method( 'getEditToken' );
WatchAction::getWatchToken( $this->watchAction->getTitle(), $user );
}

View file

@ -98,10 +98,11 @@ class ApiDeleteTest extends ApiTestCase {
// test deletion without permission
try {
$user = new User();
$apiResult = $this->doApiRequestWithToken( [
$apiResult = $this->doApiRequest( [
'action' => 'delete',
'title' => $name,
], null, $user );
'token' => $user->getEditToken(),
], null, null, $user );
} finally {
$this->assertTrue( Title::newFromText( $name )->exists() );
}

View file

@ -46,7 +46,8 @@ class ApiLogoutTest extends ApiTestCase {
$user = $this->getTestSysop()->getUser();
$this->assertTrue( $user->isRegistered(), 'sanity check' );
$token = $wgRequest->getSession()->getToken( 'logoutToken' )->toString();
// Logic copied from SkinTemplate.
$token = $user->getEditToken( 'logoutToken', $wgRequest );
$this->doUserLogout( $token, $user );
$this->assertFalse( $user->isRegistered() );

View file

@ -2,7 +2,6 @@
use MediaWiki\Block\DatabaseBlock;
use MediaWiki\MediaWikiServices;
use MediaWiki\Session\CsrfTokenSet;
/**
* @group API
@ -248,11 +247,14 @@ class ApiUserrightsTest extends ApiTestCase {
$sysop = $this->getTestSysop()->getUser();
$user = $this->getMutableTestUser()->getUser();
$res = $this->doApiRequestWithToken( [
$token = $sysop->getEditToken( $user->getName() );
$res = $this->doApiRequest( [
'action' => 'userrights',
'user' => $user->getName(),
'add' => 'sysop',
], null, $sysop );
'token' => $token,
] );
$user->clearInstanceCache();
$this->assertSame( [ 'sysop' ], $this->getServiceContainer()->getUserGroupManager()->getUserGroups( $user ) );
@ -270,17 +272,17 @@ class ApiUserrightsTest extends ApiTestCase {
* @return ApiUserrights
*/
private function getMockForProcessingExpiries( $canProcessExpiries ) {
$sysop = $this->getTestSysop()->getUser();
$user = $this->getMutableTestUser()->getUser();
$request = new FauxRequest( [
$token = $sysop->getEditToken( 'userrights' );
$main = new ApiMain( new FauxRequest( [
'action' => 'userrights',
'user' => $user->getName(),
'add' => 'sysop',
] );
$request->setVal(
'token',
( new CsrfTokenSet( $request ) )->getToken( 'userrights' )->toString()
);
$main = new ApiMain( $request );
'token' => $token,
] ) );
$mockUserRightsPage = $this->getMockBuilder( UserrightsPage::class )
->onlyMethods( [ 'canProcessExpiries' ] )

View file

@ -235,12 +235,13 @@ class ApiWatchTest extends ApiTestCase {
// This (and assertTrue below) are mostly for completeness.
$this->assertFalse( $watchlistManager->isWatched( $contextUser, $title ) );
$data = $this->doApiRequestWithToken( [
$data = $this->doApiRequest( [
'action' => 'rollback',
'title' => 'Help:UTPage',
'user' => $revUser,
'token' => $contextUser->getEditToken( 'rollback' ),
'watchlist' => 'watch'
], null, $contextUser );
] );
$this->assertArrayHasKey( 'rollback', $data[0] );
$this->assertArrayHasKey( 'title', $data[0]['rollback'] );

View file

@ -92,13 +92,11 @@ class SpecialPageExecutor {
*/
private function setEditTokenFromUser( DerivativeContext $context ) {
$request = $context->getRequest();
// Edits via GET are a security issue and should not succeed. On the other hand, not all
// POST requests are edits, but should ignore unused parameters.
if ( !$request->getCheck( 'wpEditToken' ) && $request->wasPosted() ) {
$request->setVal(
'wpEditToken',
$context->getCsrfTokenSet()->getToken()->toString()
);
$request->setVal( 'wpEditToken', $context->getUser()->getEditToken() );
}
}

View file

@ -105,6 +105,7 @@ class UploadFromUrlTest extends ApiTestCase {
* @depends testClearQueue
*/
public function testSetupUrlDownload( $data ) {
$token = $this->user->getEditToken();
$exception = false;
try {
@ -119,9 +120,10 @@ class UploadFromUrlTest extends ApiTestCase {
$exception = false;
try {
$this->doApiRequestWithToken( [
$this->doApiRequest( [
'action' => 'upload',
], $data, $this->user );
'token' => $token,
], $data );
} catch ( ApiUsageException $e ) {
$exception = true;
$this->assertEquals( 'One of the parameters "filekey", "file" and "url" is required.',
@ -131,10 +133,11 @@ class UploadFromUrlTest extends ApiTestCase {
$exception = false;
try {
$this->doApiRequestWithToken( [
$this->doApiRequest( [
'action' => 'upload',
'url' => 'http://www.example.com/test.png',
], $data, $this->user );
'token' => $token,
], $data );
} catch ( ApiUsageException $e ) {
$exception = true;
$this->assertEquals( 'The "filename" parameter must be set.', $e->getMessage() );
@ -144,11 +147,12 @@ class UploadFromUrlTest extends ApiTestCase {
$this->getServiceContainer()->getUserGroupManager()->removeUserFromGroup( $this->user, 'sysop' );
$exception = false;
try {
$this->doApiRequestWithToken( [
$this->doApiRequest( [
'action' => 'upload',
'url' => 'http://www.example.com/test.png',
'filename' => 'UploadFromUrlTest.png',
], $data, $this->user );
'token' => $token,
], $data );
} catch ( ApiUsageException $e ) {
$exception = true;
// Two error messages are possible depending on the number of groups in the wiki with upload rights:
@ -185,7 +189,7 @@ class UploadFromUrlTest extends ApiTestCase {
'filename' => 'UploadFromUrlTest.png',
'url' => 'http://upload.wikimedia.org/wikipedia/mediawiki/b/bc/Wiki.png',
'ignorewarnings' => true,
], $data, $this->user );
], $data );
$this->assertEquals( 'Success', $data[0]['upload']['result'] );
$this->deleteFile( 'UploadFromUrlTest.png' );

View file

@ -7,7 +7,6 @@ use EditPage;
use FauxRequest;
use McrUndoAction;
use MediaWiki\Revision\RevisionStoreRecord;
use MediaWiki\Session\CsrfTokenSet;
use MediaWiki\Storage\EditResult;
use MediaWiki\Storage\SlotRecord;
use MediaWikiIntegrationTestCase;
@ -422,6 +421,7 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
// after the undo, but automatic conflict resolution is not the point of
// this test anyway.
'wpTextbox1' => $newContent,
'wpEditToken' => $this->getTestSysop()->getUser()->getEditToken(),
// These two parameters are the important ones here
'wpUndidRevision' => $revisionIds[$undoIndex],
'wpUndoAfter' => $revisionIds[$undoafterIndex],
@ -432,10 +432,6 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
],
true
);
$request->setVal(
'wpEditToken',
( new CsrfTokenSet( $request ) )->getToken()->toString()
);
$editPage = new EditPage( $article );
$editPage->importFormData( $request );
@ -478,6 +474,7 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
[
// We emulate the user applying additional changes on top of the undo.
'wpTextbox1' => "line 1\n\nline 2\n\nline3 more content\n\neven more",
'wpEditToken' => $this->getTestSysop()->getUser()->getEditToken(),
'wpUndidRevision' => $revisionIds[1],
'wpUndoAfter' => $revisionIds[0],
'wpStarttime' => wfTimestampNow(),
@ -487,10 +484,7 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
],
true
);
$request->setVal(
'wpEditToken',
( new CsrfTokenSet( $request ) )->getToken()->toString()
);
$editPage = new EditPage( $article );
$editPage->importFormData( $request );
$editPage->internalAttemptSave( $result, false );
@ -536,6 +530,7 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
[
// We leave the "top" content in the textbox, as the undo should have failed
'wpTextbox1' => "line 1\n\nvandalism good content\n\nline3 more content",
'wpEditToken' => $this->getTestSysop()->getUser()->getEditToken(),
'wpUndidRevision' => $revisionIds[1],
'wpUndoAfter' => $revisionIds[0],
'wpStarttime' => wfTimestampNow(),
@ -545,10 +540,6 @@ class UndoIntegrationTest extends MediaWikiIntegrationTestCase {
],
true
);
$request->setVal(
'wpEditToken',
( new CsrfTokenSet( $request ) )->getToken()->toString()
);
$editPage = new EditPage( $article );
$editPage->importFormData( $request );

View file

@ -76,18 +76,15 @@ trait ActionModuleBasedHandlerTestTrait {
* @return ApiMain
*/
private function getApiMain( $csrfSafe = false ) {
$testContext = RequestContext::getMain();
/** @var SessionProviderInterface|MockObject $session */
$sessionProvider =
$this->createNoOpMock( SessionProviderInterface::class, [ 'safeAgainstCsrf' ] );
$sessionProvider->method( 'safeAgainstCsrf' )->willReturn( $csrfSafe );
/** @var Session|MockObject $session */
$session = $this->createNoOpMock( Session::class, [ 'getSessionId', 'getProvider', 'getUser' ] );
$session = $this->createNoOpMock( Session::class, [ 'getSessionId', 'getProvider' ] );
$session->method( 'getSessionId' )->willReturn( new SessionId( 'test' ) );
$session->method( 'getProvider' )->willReturn( $sessionProvider );
$session->method( 'getUser' )->willReturn( $testContext->getUser() );
// NOTE: This being a FauxRequest instance triggers special case behavior
// in ApiMain, causing ApiMain::isInternalMode() to return true. Among other things,
@ -99,6 +96,8 @@ trait ActionModuleBasedHandlerTestTrait {
$fauxRequest->method( 'getSession' )->willReturn( $session );
$fauxRequest->method( 'getSessionId' )->willReturn( $session->getSessionId() );
$testContext = RequestContext::getMain();
$fauxContext = new RequestContext();
$fauxContext->setRequest( $fauxRequest );
$fauxContext->setUser( $testContext->getUser() );

View file

@ -7,13 +7,10 @@ use FauxRequest;
use HashConfig;
use IContextSource;
use Language;
use MediaWiki\Session\Session;
use MediaWiki\User\UserIdentityValue;
use MediaWikiUnitTestCase;
use OutputPage;
use RequestContext;
use User;
use WebRequest;
/**
* @coversDefaultClass DerivativeContext
@ -105,23 +102,4 @@ class DerivativeContextTest extends MediaWikiUnitTestCase {
$derivativeContext->$setter( $newValue );
$this->assertSame( $newValue, $derivativeContext->$getter() );
}
/**
* @covers ::getCsrfTokenSet
*/
public function testGetCsrfTokeSetRespectsRequest() {
$context = new RequestContext();
$context->setRequest( $this->createNoOpMock( WebRequest::class ) );
$derivativeContext = new DerivativeContext( $context );
$sessionMock = $this->createNoOpMock( Session::class, [ 'getUser' ] );
$sessionMock
->method( 'getUser' )
->willReturn( UserIdentityValue::newAnonymous( '127.0.0.1' ) );
$requestMock = $this->createNoOpMock( WebRequest::class, [ 'getSession' ] );
$requestMock
->method( 'getSession' )
->willReturn( $sessionMock );
$derivativeContext->setRequest( $requestMock );
$this->assertNotNull( $derivativeContext->getCsrfTokenSet()->getToken() );
}
}