Merge "API: Overhaul token handling"

This commit is contained in:
jenkins-bot 2014-08-26 19:09:21 +00:00 committed by Gerrit Code Review
commit c1ad8bd3a7
35 changed files with 404 additions and 255 deletions

View file

@ -171,6 +171,8 @@ production.
* (bug 35045) Redirects to sections will now update the URL in browser's address
bar using the HTML5 History API. When [[Dog]] redirects to [[Animals#Dog]],
the user will now see "Animals#Dog" in their browser instead of "Dog#Dog".
* API token handling has been rewritten. Any API module using tokens will need
to be updated.
=== Bug fixes in 1.24 ===
* (bug 50572) MediaWiki:Blockip should support gender
@ -247,6 +249,10 @@ production.
automatically queries the list of submodule names from the ApiModuleManager.
* The iwurl parameter to prop=iwlinks is deprecated in favor of iwprop=url, for
parallelism with prop=langlinks.
* All tokens should be fetched from action=query&meta=tokens; all other methods
of fetching tokens are deprecated. The value needed for meta=tokens's 'type'
parameter for each module is documented in the action=help output and is
returned from action=paraminfo.
=== Languages updated in 1.24 ===

View file

@ -402,36 +402,39 @@ an action=query submodule. Use this to extend core API modules.
&$module: Module object
&$resultPageSet: ApiPageSet object
'APIQueryInfoTokens': Use this hook to add custom tokens to prop=info. Every
token has an action, which will be used in the intoken parameter and in the
output (actiontoken="..."), and a callback function which should return the
token, or false if the user isn't allowed to obtain it. The prototype of the
callback function is func($pageid, $title), where $pageid is the page ID of the
page the token is requested for and $title is the associated Title object. In
the hook, just add your callback to the $tokenFunctions array and return true
(returning false makes no sense).
'APIQueryInfoTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Use this hook to add custom tokens to prop=info. Every token has an action,
which will be used in the intoken parameter and in the output
(actiontoken="..."), and a callback function which should return the token, or
false if the user isn't allowed to obtain it. The prototype of the callback
function is func($pageid, $title), where $pageid is the page ID of the page the
token is requested for and $title is the associated Title object. In the hook,
just add your callback to the $tokenFunctions array and return true (returning
false makes no sense).
$tokenFunctions: array(action => callback)
'APIQueryRevisionsTokens': Use this hook to add custom tokens to prop=revisions.
Every token has an action, which will be used in the rvtoken parameter and in
the output (actiontoken="..."), and a callback function which should return the
token, or false if the user isn't allowed to obtain it. The prototype of the
callback function is func($pageid, $title, $rev), where $pageid is the page ID
of the page associated to the revision the token is requested for, $title the
'APIQueryRevisionsTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Use this hook to add custom tokens to prop=revisions. Every token has an
action, which will be used in the rvtoken parameter and in the output
(actiontoken="..."), and a callback function which should return the token, or
false if the user isn't allowed to obtain it. The prototype of the callback
function is func($pageid, $title, $rev), where $pageid is the page ID of the
page associated to the revision the token is requested for, $title the
associated Title object and $rev the associated Revision object. In the hook,
just add your callback to the $tokenFunctions array and return true (returning
false makes no sense).
$tokenFunctions: array(action => callback)
'APIQueryRecentChangesTokens': Use this hook to add custom tokens to
list=recentchanges. Every token has an action, which will be used in the rctoken
parameter and in the output (actiontoken="..."), and a callback function which
should return the token, or false if the user isn't allowed to obtain it. The
prototype of the callback function is func($pageid, $title, $rc), where $pageid
is the page ID of the page associated to the revision the token is requested
for, $title the associated Title object and $rc the associated RecentChange
object. In the hook, just add your callback to the $tokenFunctions array and
return true (returning false makes no sense).
'APIQueryRecentChangesTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Use this hook to add custom tokens to list=recentchanges. Every token has an
action, which will be used in the rctoken parameter and in the output
(actiontoken="..."), and a callback function which should return the token, or
false if the user isn't allowed to obtain it. The prototype of the callback
function is func($pageid, $title, $rc), where $pageid is the page ID of the
page associated to the revision the token is requested for, $title the
associated Title object and $rc the associated RecentChange object. In the
hook, just add your callback to the $tokenFunctions array and return true
(returning false makes no sense).
$tokenFunctions: array(action => callback)
'APIQuerySiteInfoGeneralInfo': Use this hook to add extra information to the
@ -443,13 +446,19 @@ $module: the current ApiQuerySiteInfo module
sites statistics information.
&$results: array of results, add things here
'APIQueryUsersTokens': Use this hook to add custom token to list=users. Every
token has an action, which will be used in the ustoken parameter and in the
output (actiontoken="..."), and a callback function which should return the
token, or false if the user isn't allowed to obtain it. The prototype of the
callback function is func($user) where $user is the User object. In the hook,
just add your callback to the $tokenFunctions array and return true (returning
false makes no sense).
'ApiQueryTokensRegisterTypes': Use this hook to add additional token types to
action=query&meta=tokens. Note that most modules will probably be able to use
the 'csrf' token instead of creating their own token types.
&$salts: array( type => salt to pass to User::getEditToken() )
'APIQueryUsersTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Use this hook to add custom token to list=users. Every token has an action,
which will be used in the ustoken parameter and in the output
(actiontoken="..."), and a callback function which should return the token, or
false if the user isn't allowed to obtain it. The prototype of the callback
function is func($user) where $user is the User object. In the hook, just add
your callback to the $tokenFunctions array and return true (returning false
makes no sense).
$tokenFunctions: array(action => callback)
'ApiMain::onException': Called by ApiMain::executeActionWithErrorHandling() when
@ -463,8 +472,8 @@ key for the array that represents the service data. In this data array, the
key-value-pair identified by the apiLink key is required.
&$apis: array of services
'ApiTokensGetTokenTypes': Use this hook to extend action=tokens with new token
types.
'ApiTokensGetTokenTypes': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Use this hook to extend action=tokens with new token types.
&$tokenTypes: supported token types in format 'type' => callback function
used to retrieve this type of tokens.

View file

@ -301,6 +301,7 @@ $wgAutoloadLocalClasses = array(
'ApiQuerySiteinfo' => 'includes/api/ApiQuerySiteinfo.php',
'ApiQueryStashImageInfo' => 'includes/api/ApiQueryStashImageInfo.php',
'ApiQueryTags' => 'includes/api/ApiQueryTags.php',
'ApiQueryTokens' => 'includes/api/ApiQueryTokens.php',
'ApiQueryUserInfo' => 'includes/api/ApiQueryUserInfo.php',
'ApiQueryUsers' => 'includes/api/ApiQueryUsers.php',
'ApiQueryWatchlist' => 'includes/api/ApiQueryWatchlist.php',

View file

@ -578,6 +578,14 @@ abstract class ApiBase extends ContextSource {
*/
public function getFinalParams( $flags = 0 ) {
$params = $this->getAllowedParams( $flags );
if ( $this->needsToken() ) {
$params['token'] = array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
);
}
wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
return $params;
@ -591,6 +599,21 @@ abstract class ApiBase extends ContextSource {
*/
public function getFinalParamDescription() {
$desc = $this->getParamDescription();
$tokenType = $this->needsToken();
if ( $tokenType ) {
if ( !isset( $desc['token'] ) ) {
$desc['token'] = array();
} elseif ( !is_array( $desc['token'] ) ) {
// We ignore a plain-string token, because it's probably an
// extension that is supplying the string for BC.
$desc['token'] = array();
}
array_unshift( $desc['token'],
"A '$tokenType' token retrieved from action=query&meta=tokens"
);
}
wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
return $desc;
@ -1992,31 +2015,84 @@ abstract class ApiBase extends ContextSource {
* @return bool
*/
public function mustBePosted() {
return false;
return $this->needsToken() !== false;
}
/**
* Returns whether this module requires a token to execute
* It is used to show possible errors in action=paraminfo
* see bug 25248
* @return bool
* Returns the token type this module requires in order to execute.
*
* Modules are strongly encouraged to use the core 'csrf' type unless they
* have specialized security needs. If the token type is not one of the
* core types, you must use the ApiQueryTokensRegisterTypes hook to
* register it.
*
* Returning a non-falsey value here will cause self::getFinalParams() to
* return a required string 'token' parameter and
* self::getFinalParamDescription() to ensure there is standardized
* documentation for it. Also, self::mustBePosted() must return true when
* tokens are used.
*
* In previous versions of MediaWiki, true was a valid return value.
* Returning true will generate errors indicating that the API module needs
* updating.
*
* @return string|false
*/
public function needsToken() {
return false;
}
/**
* Returns the token salt if there is one,
* '' if the module doesn't require a salt,
* else false if the module doesn't need a token
* You have also to override needsToken()
* Value is passed to User::getEditToken
* @return bool|string|array
* Validate the supplied token.
*
* @since 1.24
* @param string $token Supplied token
* @param array $params All supplied parameters for the module
* @return bool
*/
public function getTokenSalt() {
public final function validateToken( $token, array $params ) {
$tokenType = $this->needsToken();
$salts = ApiQueryTokens::getTokenTypeSalts();
if ( !isset( $salts[$tokenType] ) ) {
throw new MWException(
"Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
'without registering it'
);
}
if ( $this->getUser()->matchEditToken(
$token,
$salts[$tokenType],
$this->getRequest()
) ) {
return true;
}
$webUiSalt = $this->getWebUITokenSalt( $params );
if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
$token,
$webUiSalt,
$this->getRequest()
) ) {
return true;
}
return false;
}
/**
* Fetch the salt used in the Web UI corresponding to this module.
*
* Only override this if the Web UI uses a token with a non-constant salt.
*
* @since 1.24
* @param array $params All supplied parameters for the module
* @return string|array|null
*/
protected function getWebUITokenSalt( array $params ) {
return null;
}
/**
* Gets the user for whom to get the watchlist
*

View file

@ -152,7 +152,6 @@ class ApiBlock extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'token' => null,
'expiry' => 'never',
'reason' => '',
'anononly' => false,
@ -169,7 +168,6 @@ class ApiBlock extends ApiBase {
public function getParamDescription() {
return array(
'user' => 'Username, IP address or IP range you want to block',
'token' => 'A block token previously obtained through prop=info',
'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. ' .
'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
'reason' => 'Reason for block',
@ -192,17 +190,13 @@ class ApiBlock extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {
return array(
'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike',
'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail='
'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike&token=123ABC',
'api.php?action=block&user=Vandal&expiry=never&reason=Vandalism&nocreate=&autoblock=&noemail=&token=123ABC'
);
}

View file

@ -188,10 +188,6 @@ class ApiDelete extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => null,
'watch' => array(
ApiBase::PARAM_DFLT => false,
@ -220,7 +216,6 @@ class ApiDelete extends ApiBase {
return array(
'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to delete. Cannot be used together with {$p}title",
'token' => 'A delete token previously retrieved through prop=info',
'reason'
=> 'Reason for the deletion. If not set, an automatically generated reason will be used',
'watch' => 'Add the page to your watchlist',
@ -236,11 +231,7 @@ class ApiDelete extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -513,10 +513,6 @@ class ApiEditPage extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
),
'text' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'summary' => null,
'minor' => false,
'notminor' => false,
@ -575,8 +571,8 @@ class ApiEditPage extends ApiBase {
'sectiontitle' => 'The title for a new section',
'text' => 'Page content',
'token' => array(
'Edit token. You can get one of these through prop=info.',
"The token should always be sent as the last parameter, or at " .
/* Standard description is automatically prepended */
'The token should always be sent as the last parameter, or at ' .
"least, after the {$p}text parameter"
),
'summary'
@ -589,7 +585,8 @@ class ApiEditPage extends ApiBase {
'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'starttimestamp' => array(
'Timestamp when you obtained the edit token.',
'Timestamp when you began the editing process, e.g. when the current page content ' .
'was loaded for editing.',
'Used to detect edit conflicts; leave unset to ignore conflicts'
),
'recreate' => 'Override any errors about the article having been deleted in the meantime',
@ -616,11 +613,7 @@ class ApiEditPage extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -94,10 +94,6 @@ class ApiEmailUser extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'ccme' => false,
);
}
@ -107,7 +103,6 @@ class ApiEmailUser extends ApiBase {
'target' => 'User to send email to',
'subject' => 'Subject header',
'text' => 'Mail body',
'token' => 'A token previously acquired via prop=info',
'ccme' => 'Send a copy of this mail to me',
);
}
@ -117,16 +112,12 @@ class ApiEmailUser extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {
return array(
'api.php?action=emailuser&target=WikiSysop&text=Content'
'api.php?action=emailuser&target=WikiSysop&text=Content&token=123ABC'
=> 'Send an email to the User "WikiSysop" with the text "Content"',
);
}

View file

@ -132,17 +132,12 @@ class ApiFileRevert extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
);
}
public function getParamDescription() {
return array(
'filename' => 'Target filename without the File: prefix',
'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment',
'archivename' => 'Archive name of the revision to revert to',
);
@ -155,11 +150,7 @@ class ApiFileRevert extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -184,10 +184,6 @@ class ApiImageRotate extends ApiBase {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'continue' => '',
);
if ( $flags ) {
@ -202,7 +198,6 @@ class ApiImageRotate extends ApiBase {
return $pageSet->getFinalParamDescription() + array(
'rotation' => 'Degrees to rotate image clockwise',
'token' => 'Edit token. You can get one of these through action=tokens',
'continue' => 'When more results are available, use this to continue',
);
}
@ -212,11 +207,7 @@ class ApiImageRotate extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -99,10 +99,6 @@ class ApiImport extends ApiBase {
public function getAllowedParams() {
return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'summary' => null,
'xml' => array(
ApiBase::PARAM_TYPE => 'upload',
@ -122,7 +118,6 @@ class ApiImport extends ApiBase {
public function getParamDescription() {
return array(
'token' => 'Import token obtained through prop=info',
'summary' => 'Import summary',
'xml' => 'Uploaded XML file',
'interwikisource' => 'For interwiki imports: wiki to import from',
@ -143,11 +138,7 @@ class ApiImport extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -736,6 +736,11 @@ class ApiMain extends ApiBase {
}
}
if ( $this->getParameter( 'curtimestamp' ) ) {
$result->addValue( null, 'curtimestamp', wfTimestamp( TS_ISO_8601, time() ),
ApiResult::NO_SIZE_CHECK );
}
$params = $this->extractRequestParams();
$this->mAction = $params['action'];
@ -759,18 +764,35 @@ class ApiMain extends ApiBase {
}
$moduleParams = $module->extractRequestParams();
// Die if token required, but not provided
$salt = $module->getTokenSalt();
if ( $salt !== false ) {
// Check token, if necessary
if ( $module->needsToken() === true ) {
throw new MWException(
"Module '{$module->getModuleName()}' must be updated for the new token handling. " .
"See documentation for ApiBase::needsToken for details."
);
}
if ( $module->needsToken() ) {
if ( !$module->mustBePosted() ) {
throw new MWException(
"Module '{$module->getModuleName()}' must require POST to use tokens."
);
}
if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) );
}
if ( !$this->getUser()->matchEditToken(
$moduleParams['token'],
$salt,
$this->getContext()->getRequest() )
) {
if ( array_key_exists(
$module->encodeParamName( 'token' ),
$this->getRequest()->getQueryValues()
) ) {
$this->dieUsage(
"The '{$module->encodeParamName( 'token' )}' parameter must be POSTed",
'mustposttoken'
);
}
if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
$this->dieUsageMsg( 'sessionfailure' );
}
}
@ -1112,6 +1134,7 @@ class ApiMain extends ApiBase {
),
'requestid' => null,
'servedby' => false,
'curtimestamp' => false,
'origin' => null,
);
}
@ -1139,6 +1162,7 @@ class ApiMain extends ApiBase {
'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
'servedby' => 'Include the hostname that served the request in the ' .
'results. Unconditionally shown on error',
'curtimestamp' => 'Include the current timestamp in the result.',
'origin' => array(
'When accessing the API using a cross-domain AJAX request (CORS), set this to the',
'originating domain. This must be included in any pre-flight request, and',

View file

@ -195,10 +195,6 @@ class ApiMove extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => '',
'movetalk' => false,
'movesubpages' => false,
@ -231,7 +227,6 @@ class ApiMove extends ApiBase {
'from' => "Title of the page you want to move. Cannot be used together with {$p}fromid",
'fromid' => "Page ID of the page you want to move. Cannot be used together with {$p}from",
'to' => 'Title you want to rename the page to',
'token' => 'A move token previously retrieved through prop=info',
'reason' => 'Reason for the move',
'movetalk' => 'Move the talk page, if it exists',
'movesubpages' => 'Move subpages, if applicable',
@ -249,11 +244,7 @@ class ApiMove extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -135,10 +135,6 @@ class ApiOptions extends ApiBase {
$optionKinds[] = 'all';
return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reset' => false,
'resetkinds' => array(
ApiBase::PARAM_TYPE => $optionKinds,
@ -159,7 +155,6 @@ class ApiOptions extends ApiBase {
public function getParamDescription() {
return array(
'token' => 'An options token previously obtained through the action=tokens',
'reset' => 'Resets preferences to the site defaults',
'resetkinds' => 'List of types of options to reset when the "reset" option is set',
'change' => array( 'List of changes, formatted name=value (e.g. skin=vector), ' .
@ -183,11 +178,7 @@ class ApiOptions extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getHelpUrls() {

View file

@ -198,6 +198,10 @@ class ApiParamInfo extends ApiBase {
$a['required'] = '';
}
if ( $n === 'token' && $obj->needsToken() ) {
$a['tokentype'] = $obj->needsToken();
}
if ( isset( $p[ApiBase::PARAM_DFLT] ) ) {
$type = $p[ApiBase::PARAM_TYPE];
if ( $type === 'boolean' ) {

View file

@ -77,10 +77,6 @@ class ApiPatrol extends ApiBase {
public function getAllowedParams() {
return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'rcid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
@ -92,7 +88,6 @@ class ApiPatrol extends ApiBase {
public function getParamDescription() {
return array(
'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
'revid' => 'Revision ID to patrol',
);
@ -103,17 +98,13 @@ class ApiPatrol extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return 'patrol';
}
public function getExamples() {
return array(
'api.php?action=patrol&token=123abc&rcid=230672766',
'api.php?action=patrol&token=123abc&revid=230672766'
'api.php?action=patrol&token=123ABC&rcid=230672766',
'api.php?action=patrol&token=123ABC&revid=230672766'
);
}

View file

@ -148,10 +148,6 @@ class ApiProtect extends ApiBase {
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer',
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'protections' => array(
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_REQUIRED => true,
@ -185,7 +181,6 @@ class ApiProtect extends ApiBase {
return array(
'title' => "Title of the page you want to (un)protect. Cannot be used together with {$p}pageid",
'pageid' => "ID of the page you want to (un)protect. Cannot be used together with {$p}title",
'token' => 'A protect token previously retrieved through prop=info',
'protections' => 'List of protection levels, formatted action=group (e.g. edit=sysop)',
'expiry' => array(
'Expiry timestamps. If only one timestamp is ' .
@ -208,11 +203,7 @@ class ApiProtect extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -108,6 +108,7 @@ class ApiQuery extends ApiBase {
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
'filerepoinfo' => 'ApiQueryFileRepoInfo',
'tokens' => 'ApiQueryTokens',
);
/**

View file

@ -61,6 +61,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_token = isset( $prop['token'] );
$fld_tags = isset( $prop['tags'] );
if ( isset( $prop['token'] ) ) {
$p = $this->getModulePrefix();
$this->setWarning(
"{$p}prop=token has been deprecated. Please use action=query&meta=tokens instead."
);
}
// If we're in JSON callback mode, no tokens can be obtained
if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
$fld_token = false;
@ -493,7 +500,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
' len - Adds the length (bytes) of the revision',
' sha1 - Adds the SHA-1 (base 16) of the revision',
' content - Adds the content of the revision',
' token - Gives the edit token',
' token - DEPRECATED! Gives the edit token',
' tags - Tags for the revision',
),
'namespace' => 'Only list pages in this namespace (3)',

View file

@ -79,6 +79,7 @@ class ApiQueryInfo extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title)
* it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array(tokenname => function)
*/
protected function getTokenFunctions() {
@ -110,10 +111,16 @@ class ApiQueryInfo extends ApiQueryBase {
static protected $cachedTokens = array();
/**
* @deprecated since 1.24
*/
public static function resetTokenCache() {
ApiQueryInfo::$cachedTokens = array();
}
/**
* @deprecated since 1.24
*/
public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose
@ -131,6 +138,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['edit'];
}
/**
* @deprecated since 1.24
*/
public static function getDeleteToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'delete' ) ) {
@ -145,6 +155,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['delete'];
}
/**
* @deprecated since 1.24
*/
public static function getProtectToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'protect' ) ) {
@ -159,6 +172,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['protect'];
}
/**
* @deprecated since 1.24
*/
public static function getMoveToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'move' ) ) {
@ -173,6 +189,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['move'];
}
/**
* @deprecated since 1.24
*/
public static function getBlockToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'block' ) ) {
@ -187,11 +206,17 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['block'];
}
/**
* @deprecated since 1.24
*/
public static function getUnblockToken( $pageid, $title ) {
// Currently, this is exactly the same as the block token
return self::getBlockToken( $pageid, $title );
}
/**
* @deprecated since 1.24
*/
public static function getEmailToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) {
@ -206,6 +231,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['email'];
}
/**
* @deprecated since 1.24
*/
public static function getImportToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
@ -220,6 +248,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['import'];
}
/**
* @deprecated since 1.24
*/
public static function getWatchToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@ -234,6 +265,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['watch'];
}
/**
* @deprecated since 1.24
*/
public static function getOptionsToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
@ -784,6 +818,7 @@ class ApiQueryInfo extends ApiQueryBase {
// need to be added to getCacheMode()
) ),
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null,
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )

View file

@ -47,6 +47,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc)
* it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array(tokenname => function)
*/
protected function getTokenFunctions() {
@ -69,6 +70,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
}
/**
* @deprecated since 1.24
* @param int $pageid
* @param Title $title
* @param RecentChange|null $rc
@ -657,6 +659,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
)
),
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),

View file

@ -48,6 +48,7 @@ class ApiQueryRevisions extends ApiQueryBase {
private $tokenFunctions;
/** @deprecated since 1.24 */
protected function getTokenFunctions() {
// tokenname => function
// function prototype is func($pageid, $title, $rev)
@ -72,6 +73,7 @@ class ApiQueryRevisions extends ApiQueryBase {
}
/**
* @deprecated since 1.24
* @param int $pageid
* @param Title $title
* @param Revision $rev
@ -748,6 +750,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'parse' => false,
'section' => null,
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),

View file

@ -0,0 +1,104 @@
<?php
/**
* Module to fetch tokens via action=query&meta=tokens
*
* Created on August 8, 2014
*
* Copyright © 2014 Brad Jorsch bjorsch@wikimedia.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @since 1.24
*/
/**
* Module to fetch tokens via action=query&meta=tokens
*
* @ingroup API
* @since 1.24
*/
class ApiQueryTokens extends ApiQueryBase {
public function execute() {
$params = $this->extractRequestParams();
$res = array();
if ( $this->getMain()->getRequest()->getVal( 'callback' ) !== null ) {
$this->setWarning( 'Tokens may not be obtained when using a callback' );
return;
}
$salts = self::getTokenTypeSalts();
foreach ( $params['type'] as $type ) {
$salt = $salts[$type];
$val = $this->getUser()->getEditToken( $salt, $this->getRequest() );
$res[$type . 'token'] = $val;
}
$this->getResult()->addValue( 'query', $this->getModuleName(), $res );
}
public static function getTokenTypeSalts() {
static $salts = null;
if ( !$salts ) {
wfProfileIn( __METHOD__ );
$salts = array(
'csrf' => '',
'watch' => 'watch',
'patrol' => 'patrol',
'rollback' => 'rollback',
'userrights' => 'userrights',
);
wfRunHooks( 'ApiQueryTokensRegisterTypes', array( &$salts ) );
ksort( $salts );
wfProfileOut( __METHOD__ );
}
return $salts;
}
public function getAllowedParams() {
return array(
'type' => array(
ApiBase::PARAM_DFLT => 'csrf',
ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( self::getTokenTypeSalts() ),
),
);
}
public function getParamDescription() {
return array(
'type' => 'Type of token(s) to request'
);
}
public function getDescription() {
return 'Gets tokens for data-modifying actions.';
}
protected function getExamples() {
return array(
'api.php?action=query&meta=tokens' => 'Retrieve a csrf token (the default)',
'api.php?action=query&meta=tokens&type=watch|patrol' => 'Retrieve a watch token and a patrol token'
);
}
public function getCacheMode( $params ) {
return 'private';
}
}

View file

@ -104,6 +104,12 @@ class ApiQueryUserInfo extends ApiQueryBase {
$vals['options'] = $user->getOptions();
}
if ( isset( $this->prop['preferencestoken'] ) ) {
$p = $this->getModulePrefix();
$this->setWarning(
"{$p}prop=preferencestoken has been deprecated. Please use action=query&meta=tokens instead."
);
}
if ( isset( $this->prop['preferencestoken'] ) &&
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) &&
$user->isAllowed( 'editmyoptions' )
@ -252,7 +258,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
' rights - Lists all the rights the current user has',
' changeablegroups - Lists the groups the current user can add to and remove from',
' options - Lists all preferences the current user has set',
' preferencestoken - Get a token to change current user\'s preferences',
' preferencestoken - DEPRECATED! Get a token to change current user\'s preferences',
' editcount - Adds the current user\'s edit count',
' ratelimits - Lists all rate limits applying to the current user',
' realname - Adds the user\'s real name',

View file

@ -58,6 +58,7 @@ class ApiQueryUsers extends ApiQueryBase {
* Get an array mapping token names to their handler functions.
* The prototype for a token function is func($user)
* it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array of tokenname => function
*/
protected function getTokenFunctions() {
@ -80,6 +81,7 @@ class ApiQueryUsers extends ApiQueryBase {
}
/**
* @deprecated since 1.24
* @param User $user
* @return string
*/
@ -317,6 +319,7 @@ class ApiQueryUsers extends ApiQueryBase {
ApiBase::PARAM_ISMULTI => true
),
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),

View file

@ -195,10 +195,6 @@ class ApiRevisionDelete extends ApiBase {
ApiBase::PARAM_TYPE => array( 'yes', 'no', 'nochange' ),
ApiBase::PARAM_DFLT => 'nochange',
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => null,
);
}
@ -211,7 +207,6 @@ class ApiRevisionDelete extends ApiBase {
'hide' => 'What to hide for each revision',
'show' => 'What to unhide for each revision',
'suppress' => 'Whether to suppress data from administrators as well as others',
'token' => 'A delete token previously retrieved through action=tokens',
'reason' => 'Reason for the deletion/undeletion',
);
}
@ -221,11 +216,7 @@ class ApiRevisionDelete extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -40,9 +40,19 @@ class ApiRollback extends ApiBase {
private $mUser = null;
public function execute() {
$user = $this->getUser();
$params = $this->extractRequestParams();
// User and title already validated in call to getTokenSalt from Main
// WikiPage::doRollback needs a Web UI token, so get one of those if we
// validated based on an API rollback token.
$token = $params['token'];
if ( $user->matchEditToken( $token, 'rollback', $this->getRequest() ) ) {
$token = $this->getUser()->getEditToken(
$this->getWebUITokenSalt( $params ),
$this->getRequest()
);
}
$titleObj = $this->getRbTitle( $params );
$pageObj = WikiPage::factory( $titleObj );
$summary = $params['summary'];
@ -50,10 +60,10 @@ class ApiRollback extends ApiBase {
$retval = $pageObj->doRollback(
$this->getRbUser( $params ),
$summary,
$params['token'],
$token,
$params['markbot'],
$details,
$this->getUser()
$user
);
if ( $retval ) {
@ -99,10 +109,6 @@ class ApiRollback extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'summary' => '',
'markbot' => false,
'watchlist' => array(
@ -123,10 +129,11 @@ class ApiRollback extends ApiBase {
return array(
'title' => "Title of the page you want to roll back. Cannot be used together with {$p}pageid",
'pageid' => "Page ID of the page you want to roll back. Cannot be used together with {$p}title",
'user' => 'Name of the user whose edits are to be rolled back. If ' .
'set incorrectly, you\'ll get a badtoken error.',
'token' => 'A rollback token previously retrieved through ' .
"{$this->getModulePrefix()}prop=revisions",
'user' => 'Name of the user whose edits are to be rolled back.',
'token' => array(
/* Standard description automatically prepended */
'For compatibility, the token used in the web UI is also accepted.'
),
'summary' => 'Custom edit summary. If empty, default summary will be used',
'markbot' => 'Mark the reverted edits and the revert as bot edits',
'watchlist' => 'Unconditionally add or remove the page from your watchlist, ' .
@ -142,12 +149,10 @@ class ApiRollback extends ApiBase {
}
public function needsToken() {
return true;
return 'rollback';
}
public function getTokenSalt() {
$params = $this->extractRequestParams();
protected function getWebUITokenSalt( array $params ) {
return array(
$this->getRbTitle( $params )->getPrefixedText(),
$this->getRbUser( $params )

View file

@ -202,11 +202,7 @@ class ApiSetNotificationTimestamp extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getAllowedParams( $flags = 0 ) {
@ -214,7 +210,6 @@ class ApiSetNotificationTimestamp extends ApiBase {
'entirewatchlist' => array(
ApiBase::PARAM_TYPE => 'boolean'
),
'token' => null,
'timestamp' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
@ -239,7 +234,6 @@ class ApiSetNotificationTimestamp extends ApiBase {
'timestamp' => 'Timestamp to which to set the notification timestamp',
'torevid' => 'Revision to set the notification timestamp to (one page only)',
'newerthanrevid' => 'Revision to set the notification timestamp newer than (one page only)',
'token' => 'A token previously acquired via prop=info',
'continue' => 'When more results are available, use this to continue',
);
}

View file

@ -25,11 +25,16 @@
*/
/**
* @deprecated since 1.24
* @ingroup API
*/
class ApiTokens extends ApiBase {
public function execute() {
$this->setWarning(
"action=tokens has been deprecated. Please use action=query&meta=tokens instead."
);
$params = $this->extractRequestParams();
$res = array();
@ -88,7 +93,10 @@ class ApiTokens extends ApiBase {
}
public function getDescription() {
return 'Gets tokens for data-modifying actions.';
return array(
'This module is deprecated in favor of action=query&meta=tokens.',
'Gets tokens for data-modifying actions.'
);
}
protected function getExamples() {

View file

@ -89,7 +89,6 @@ class ApiUnblock extends ApiBase {
ApiBase::PARAM_TYPE => 'integer',
),
'user' => null,
'token' => null,
'reason' => '',
);
}
@ -102,7 +101,6 @@ class ApiUnblock extends ApiBase {
"Cannot be used together with {$p}user",
'user' => "Username, IP address or IP range you want to unblock. " .
"Cannot be used together with {$p}id",
'token' => "An unblock token previously obtained through prop=info",
'reason' => 'Reason for unblock',
);
}
@ -112,11 +110,7 @@ class ApiUnblock extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -96,10 +96,6 @@ class ApiUndelete extends ApiBase {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => '',
'timestamps' => array(
ApiBase::PARAM_TYPE => 'timestamp',
@ -124,10 +120,6 @@ class ApiUndelete extends ApiBase {
public function getParamDescription() {
return array(
'title' => 'Title of the page you want to restore',
'token' => array(
'An undelete token previously retrieved through list=deletedrevs, or ',
'a delete token retrieved through action=tokens.'
),
'reason' => 'Reason for restoring',
'timestamps' => array(
'Timestamps of the revisions to restore.',
@ -151,11 +143,7 @@ class ApiUndelete extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {

View file

@ -688,10 +688,6 @@ class ApiUpload extends ApiBase {
ApiBase::PARAM_DFLT => ''
),
'text' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'watch' => array(
ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true,
@ -735,7 +731,6 @@ class ApiUpload extends ApiBase {
public function getParamDescription() {
$params = array(
'filename' => 'Target filename',
'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment. Also used as the initial page text for new ' .
'files if "text" is not specified',
'text' => 'Initial page text for new files',
@ -771,24 +766,20 @@ class ApiUpload extends ApiBase {
' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter',
' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter',
'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when',
'sending the "file". Also you must get and send an edit token before doing any upload stuff.'
'sending the "file".',
);
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return '';
return 'csrf';
}
public function getExamples() {
return array(
'api.php?action=upload&filename=Wiki.png' .
'&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png'
'&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png&token=123ABC'
=> 'Upload from a URL',
'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1'
'api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1&token=123ABC'
=> 'Complete an upload that failed due to warnings',
);
}

View file

@ -35,7 +35,7 @@ class ApiUserrights extends ApiBase {
public function execute() {
$params = $this->extractRequestParams();
$user = $this->getUrUser();
$user = $this->getUrUser( $params );
$form = new UserrightsPage;
$form->setContext( $this->getContext() );
@ -53,14 +53,14 @@ class ApiUserrights extends ApiBase {
}
/**
* @param array $params
* @return User
*/
private function getUrUser() {
private function getUrUser( array $params ) {
if ( $this->mUser !== null ) {
return $this->mUser;
}
$params = $this->extractRequestParams();
$this->requireOnlyOneParameter( $params, 'user', 'userid' );
$user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid'];
@ -101,10 +101,6 @@ class ApiUserrights extends ApiBase {
ApiBase::PARAM_TYPE => User::getAllGroups(),
ApiBase::PARAM_ISMULTI => true
),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => array(
ApiBase::PARAM_DFLT => ''
)
@ -117,7 +113,10 @@ class ApiUserrights extends ApiBase {
'userid' => 'User id',
'add' => 'Add the user to these groups',
'remove' => 'Remove the user from these groups',
'token' => 'A userrights token previously retrieved through list=users',
'token' => array(
/* Standard description automatically prepended */
'For compatibility, the token used in the web UI is also accepted.'
),
'reason' => 'Reason for the change',
);
}
@ -127,11 +126,11 @@ class ApiUserrights extends ApiBase {
}
public function needsToken() {
return true;
return 'userrights';
}
public function getTokenSalt() {
return $this->getUrUser()->getName();
protected function getWebUITokenSalt( array $params ) {
return $this->getUrUser( $params )->getName();
}
public function getExamples() {

View file

@ -166,10 +166,6 @@ class ApiWatch extends ApiBase {
}
public function needsToken() {
return true;
}
public function getTokenSalt() {
return 'watch';
}
@ -181,10 +177,6 @@ class ApiWatch extends ApiBase {
),
'unwatch' => false,
'uselang' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'continue' => '',
);
if ( $flags ) {
@ -201,7 +193,6 @@ class ApiWatch extends ApiBase {
'title' => 'The page to (un)watch. use titles instead',
'unwatch' => 'If set the page will be unwatched rather than watched',
'uselang' => 'Language to show the message in',
'token' => 'A token previously acquired via prop=info',
'continue' => 'When more results are available, use this to continue',
);
}

View file

@ -44,8 +44,8 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule {
return array(
'editToken' => $wgUser->getEditToken(),
'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ),
'watchToken' => ApiQueryInfo::getWatchToken( null, null ),
'patrolToken' => $wgUser->getEditToken( 'patrol' ),
'watchToken' => $wgUser->getEditToken( 'watch' ),
);
}