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 * (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]], 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". 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 fixes in 1.24 ===
* (bug 50572) MediaWiki:Blockip should support gender * (bug 50572) MediaWiki:Blockip should support gender
@ -247,6 +249,10 @@ production.
automatically queries the list of submodule names from the ApiModuleManager. automatically queries the list of submodule names from the ApiModuleManager.
* The iwurl parameter to prop=iwlinks is deprecated in favor of iwprop=url, for * The iwurl parameter to prop=iwlinks is deprecated in favor of iwprop=url, for
parallelism with prop=langlinks. 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 === === 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 &$module: Module object
&$resultPageSet: ApiPageSet object &$resultPageSet: ApiPageSet object
'APIQueryInfoTokens': Use this hook to add custom tokens to prop=info. Every 'APIQueryInfoTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
token has an action, which will be used in the intoken parameter and in the Use this hook to add custom tokens to prop=info. Every token has an action,
output (actiontoken="..."), and a callback function which should return the which will be used in the intoken parameter and in the output
token, or false if the user isn't allowed to obtain it. The prototype of the (actiontoken="..."), and a callback function which should return the token, or
callback function is func($pageid, $title), where $pageid is the page ID of the false if the user isn't allowed to obtain it. The prototype of the callback
page the token is requested for and $title is the associated Title object. In function is func($pageid, $title), where $pageid is the page ID of the page the
the hook, just add your callback to the $tokenFunctions array and return true token is requested for and $title is the associated Title object. In the hook,
(returning false makes no sense). just add your callback to the $tokenFunctions array and return true (returning
false makes no sense).
$tokenFunctions: array(action => callback) $tokenFunctions: array(action => callback)
'APIQueryRevisionsTokens': Use this hook to add custom tokens to prop=revisions. 'APIQueryRevisionsTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
Every token has an action, which will be used in the rvtoken parameter and in Use this hook to add custom tokens to prop=revisions. Every token has an
the output (actiontoken="..."), and a callback function which should return the action, which will be used in the rvtoken parameter and in the output
token, or false if the user isn't allowed to obtain it. The prototype of the (actiontoken="..."), and a callback function which should return the token, or
callback function is func($pageid, $title, $rev), where $pageid is the page ID false if the user isn't allowed to obtain it. The prototype of the callback
of the page associated to the revision the token is requested for, $title the 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, associated Title object and $rev the associated Revision object. In the hook,
just add your callback to the $tokenFunctions array and return true (returning just add your callback to the $tokenFunctions array and return true (returning
false makes no sense). false makes no sense).
$tokenFunctions: array(action => callback) $tokenFunctions: array(action => callback)
'APIQueryRecentChangesTokens': Use this hook to add custom tokens to 'APIQueryRecentChangesTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
list=recentchanges. Every token has an action, which will be used in the rctoken Use this hook to add custom tokens to list=recentchanges. Every token has an
parameter and in the output (actiontoken="..."), and a callback function which action, which will be used in the rctoken parameter and in the output
should return the token, or false if the user isn't allowed to obtain it. The (actiontoken="..."), and a callback function which should return the token, or
prototype of the callback function is func($pageid, $title, $rc), where $pageid false if the user isn't allowed to obtain it. The prototype of the callback
is the page ID of the page associated to the revision the token is requested function is func($pageid, $title, $rc), where $pageid is the page ID of the
for, $title the associated Title object and $rc the associated RecentChange page associated to the revision the token is requested for, $title the
object. In the hook, just add your callback to the $tokenFunctions array and associated Title object and $rc the associated RecentChange object. In the
return true (returning false makes no sense). hook, just add your callback to the $tokenFunctions array and return true
(returning false makes no sense).
$tokenFunctions: array(action => callback) $tokenFunctions: array(action => callback)
'APIQuerySiteInfoGeneralInfo': Use this hook to add extra information to the 'APIQuerySiteInfoGeneralInfo': Use this hook to add extra information to the
@ -443,13 +446,19 @@ $module: the current ApiQuerySiteInfo module
sites statistics information. sites statistics information.
&$results: array of results, add things here &$results: array of results, add things here
'APIQueryUsersTokens': Use this hook to add custom token to list=users. Every 'ApiQueryTokensRegisterTypes': Use this hook to add additional token types to
token has an action, which will be used in the ustoken parameter and in the action=query&meta=tokens. Note that most modules will probably be able to use
output (actiontoken="..."), and a callback function which should return the the 'csrf' token instead of creating their own token types.
token, or false if the user isn't allowed to obtain it. The prototype of the &$salts: array( type => salt to pass to User::getEditToken() )
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 'APIQueryUsersTokens': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
false makes no sense). 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) $tokenFunctions: array(action => callback)
'ApiMain::onException': Called by ApiMain::executeActionWithErrorHandling() when '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. key-value-pair identified by the apiLink key is required.
&$apis: array of services &$apis: array of services
'ApiTokensGetTokenTypes': Use this hook to extend action=tokens with new token 'ApiTokensGetTokenTypes': DEPRECATED! Use ApiQueryTokensRegisterTypes instead.
types. Use this hook to extend action=tokens with new token types.
&$tokenTypes: supported token types in format 'type' => callback function &$tokenTypes: supported token types in format 'type' => callback function
used to retrieve this type of tokens. used to retrieve this type of tokens.

View file

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

View file

@ -578,6 +578,14 @@ abstract class ApiBase extends ContextSource {
*/ */
public function getFinalParams( $flags = 0 ) { public function getFinalParams( $flags = 0 ) {
$params = $this->getAllowedParams( $flags ); $params = $this->getAllowedParams( $flags );
if ( $this->needsToken() ) {
$params['token'] = array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true,
);
}
wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) ); wfRunHooks( 'APIGetAllowedParams', array( &$this, &$params, $flags ) );
return $params; return $params;
@ -591,6 +599,21 @@ abstract class ApiBase extends ContextSource {
*/ */
public function getFinalParamDescription() { public function getFinalParamDescription() {
$desc = $this->getParamDescription(); $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 ) ); wfRunHooks( 'APIGetParamDescription', array( &$this, &$desc ) );
return $desc; return $desc;
@ -1992,31 +2015,84 @@ abstract class ApiBase extends ContextSource {
* @return bool * @return bool
*/ */
public function mustBePosted() { public function mustBePosted() {
return false; return $this->needsToken() !== false;
} }
/** /**
* Returns whether this module requires a token to execute * Returns the token type this module requires in order to execute.
* It is used to show possible errors in action=paraminfo *
* see bug 25248 * Modules are strongly encouraged to use the core 'csrf' type unless they
* @return bool * 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() { public function needsToken() {
return false; return false;
} }
/** /**
* Returns the token salt if there is one, * Validate the supplied token.
* '' if the module doesn't require a salt, *
* else false if the module doesn't need a token * @since 1.24
* You have also to override needsToken() * @param string $token Supplied token
* Value is passed to User::getEditToken * @param array $params All supplied parameters for the module
* @return bool|string|array * @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; 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 * 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_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true ApiBase::PARAM_REQUIRED => true
), ),
'token' => null,
'expiry' => 'never', 'expiry' => 'never',
'reason' => '', 'reason' => '',
'anononly' => false, 'anononly' => false,
@ -169,7 +168,6 @@ class ApiBlock extends ApiBase {
public function getParamDescription() { public function getParamDescription() {
return array( return array(
'user' => 'Username, IP address or IP range you want to block', '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\'. ' . 'expiry' => 'Relative expiry time, e.g. \'5 months\' or \'2 weeks\'. ' .
'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.', 'If set to \'infinite\', \'indefinite\' or \'never\', the block will never expire.',
'reason' => 'Reason for block', 'reason' => 'Reason for block',
@ -192,17 +190,13 @@ class ApiBlock extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {
return array( return array(
'api.php?action=block&user=123.5.5.12&expiry=3%20days&reason=First%20strike', '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=' '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( 'pageid' => array(
ApiBase::PARAM_TYPE => 'integer' ApiBase::PARAM_TYPE => 'integer'
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => null, 'reason' => null,
'watch' => array( 'watch' => array(
ApiBase::PARAM_DFLT => false, ApiBase::PARAM_DFLT => false,
@ -220,7 +216,6 @@ class ApiDelete extends ApiBase {
return array( return array(
'title' => "Title of the page you want to delete. Cannot be used together with {$p}pageid", '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", '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'
=> 'Reason for the deletion. If not set, an automatically generated reason will be used', => 'Reason for the deletion. If not set, an automatically generated reason will be used',
'watch' => 'Add the page to your watchlist', 'watch' => 'Add the page to your watchlist',
@ -236,11 +231,7 @@ class ApiDelete extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {

View file

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

View file

@ -94,10 +94,6 @@ class ApiEmailUser extends ApiBase {
ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true ApiBase::PARAM_REQUIRED => true
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'ccme' => false, 'ccme' => false,
); );
} }
@ -107,7 +103,6 @@ class ApiEmailUser extends ApiBase {
'target' => 'User to send email to', 'target' => 'User to send email to',
'subject' => 'Subject header', 'subject' => 'Subject header',
'text' => 'Mail body', 'text' => 'Mail body',
'token' => 'A token previously acquired via prop=info',
'ccme' => 'Send a copy of this mail to me', 'ccme' => 'Send a copy of this mail to me',
); );
} }
@ -117,16 +112,12 @@ class ApiEmailUser extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {
return array( 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"', => '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_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true, ApiBase::PARAM_REQUIRED => true,
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
); );
} }
public function getParamDescription() { public function getParamDescription() {
return array( return array(
'filename' => 'Target filename without the File: prefix', 'filename' => 'Target filename without the File: prefix',
'token' => 'Edit token. You can get one of these through prop=info',
'comment' => 'Upload comment', 'comment' => 'Upload comment',
'archivename' => 'Archive name of the revision to revert to', 'archivename' => 'Archive name of the revision to revert to',
); );
@ -155,11 +150,7 @@ class ApiFileRevert extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {

View file

@ -184,10 +184,6 @@ class ApiImageRotate extends ApiBase {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ), ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true ApiBase::PARAM_REQUIRED => true
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'continue' => '', 'continue' => '',
); );
if ( $flags ) { if ( $flags ) {
@ -202,7 +198,6 @@ class ApiImageRotate extends ApiBase {
return $pageSet->getFinalParamDescription() + array( return $pageSet->getFinalParamDescription() + array(
'rotation' => 'Degrees to rotate image clockwise', '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', 'continue' => 'When more results are available, use this to continue',
); );
} }
@ -212,11 +207,7 @@ class ApiImageRotate extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {

View file

@ -99,10 +99,6 @@ class ApiImport extends ApiBase {
public function getAllowedParams() { public function getAllowedParams() {
return array( return array(
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'summary' => null, 'summary' => null,
'xml' => array( 'xml' => array(
ApiBase::PARAM_TYPE => 'upload', ApiBase::PARAM_TYPE => 'upload',
@ -122,7 +118,6 @@ class ApiImport extends ApiBase {
public function getParamDescription() { public function getParamDescription() {
return array( return array(
'token' => 'Import token obtained through prop=info',
'summary' => 'Import summary', 'summary' => 'Import summary',
'xml' => 'Uploaded XML file', 'xml' => 'Uploaded XML file',
'interwikisource' => 'For interwiki imports: wiki to import from', 'interwikisource' => 'For interwiki imports: wiki to import from',
@ -143,11 +138,7 @@ class ApiImport extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { 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(); $params = $this->extractRequestParams();
$this->mAction = $params['action']; $this->mAction = $params['action'];
@ -759,18 +764,35 @@ class ApiMain extends ApiBase {
} }
$moduleParams = $module->extractRequestParams(); $moduleParams = $module->extractRequestParams();
// Die if token required, but not provided // Check token, if necessary
$salt = $module->getTokenSalt(); if ( $module->needsToken() === true ) {
if ( $salt !== false ) { 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'] ) ) { if ( !isset( $moduleParams['token'] ) ) {
$this->dieUsageMsg( array( 'missingparam', 'token' ) ); $this->dieUsageMsg( array( 'missingparam', 'token' ) );
} }
if ( !$this->getUser()->matchEditToken( if ( array_key_exists(
$moduleParams['token'], $module->encodeParamName( 'token' ),
$salt, $this->getRequest()->getQueryValues()
$this->getContext()->getRequest() ) ) ) {
) { $this->dieUsage(
"The '{$module->encodeParamName( 'token' )}' parameter must be POSTed",
'mustposttoken'
);
}
if ( !$module->validateToken( $moduleParams['token'], $moduleParams ) ) {
$this->dieUsageMsg( 'sessionfailure' ); $this->dieUsageMsg( 'sessionfailure' );
} }
} }
@ -1112,6 +1134,7 @@ class ApiMain extends ApiBase {
), ),
'requestid' => null, 'requestid' => null,
'servedby' => false, 'servedby' => false,
'curtimestamp' => false,
'origin' => null, 'origin' => null,
); );
} }
@ -1139,6 +1162,7 @@ class ApiMain extends ApiBase {
'requestid' => 'Request ID to distinguish requests. This will just be output back to you', 'requestid' => 'Request ID to distinguish requests. This will just be output back to you',
'servedby' => 'Include the hostname that served the request in the ' . 'servedby' => 'Include the hostname that served the request in the ' .
'results. Unconditionally shown on error', 'results. Unconditionally shown on error',
'curtimestamp' => 'Include the current timestamp in the result.',
'origin' => array( 'origin' => array(
'When accessing the API using a cross-domain AJAX request (CORS), set this to the', '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', '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_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true ApiBase::PARAM_REQUIRED => true
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => '', 'reason' => '',
'movetalk' => false, 'movetalk' => false,
'movesubpages' => 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", '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", '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', 'to' => 'Title you want to rename the page to',
'token' => 'A move token previously retrieved through prop=info',
'reason' => 'Reason for the move', 'reason' => 'Reason for the move',
'movetalk' => 'Move the talk page, if it exists', 'movetalk' => 'Move the talk page, if it exists',
'movesubpages' => 'Move subpages, if applicable', 'movesubpages' => 'Move subpages, if applicable',
@ -249,11 +244,7 @@ class ApiMove extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -61,6 +61,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
$fld_token = isset( $prop['token'] ); $fld_token = isset( $prop['token'] );
$fld_tags = isset( $prop['tags'] ); $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 we're in JSON callback mode, no tokens can be obtained
if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) { if ( !is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) ) {
$fld_token = false; $fld_token = false;
@ -493,7 +500,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
' len - Adds the length (bytes) of the revision', ' len - Adds the length (bytes) of the revision',
' sha1 - Adds the SHA-1 (base 16) of the revision', ' sha1 - Adds the SHA-1 (base 16) of the revision',
' content - Adds the content 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', ' tags - Tags for the revision',
), ),
'namespace' => 'Only list pages in this namespace (3)', '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. * Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title) * The prototype for a token function is func($pageid, $title)
* it should return a token or false (permission denied) * it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array(tokenname => function) * @return array Array(tokenname => function)
*/ */
protected function getTokenFunctions() { protected function getTokenFunctions() {
@ -110,10 +111,16 @@ class ApiQueryInfo extends ApiQueryBase {
static protected $cachedTokens = array(); static protected $cachedTokens = array();
/**
* @deprecated since 1.24
*/
public static function resetTokenCache() { public static function resetTokenCache() {
ApiQueryInfo::$cachedTokens = array(); ApiQueryInfo::$cachedTokens = array();
} }
/**
* @deprecated since 1.24
*/
public static function getEditToken( $pageid, $title ) { public static function getEditToken( $pageid, $title ) {
// We could check for $title->userCan('edit') here, // We could check for $title->userCan('edit') here,
// but that's too expensive for this purpose // but that's too expensive for this purpose
@ -131,6 +138,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['edit']; return ApiQueryInfo::$cachedTokens['edit'];
} }
/**
* @deprecated since 1.24
*/
public static function getDeleteToken( $pageid, $title ) { public static function getDeleteToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isAllowed( 'delete' ) ) { if ( !$wgUser->isAllowed( 'delete' ) ) {
@ -145,6 +155,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['delete']; return ApiQueryInfo::$cachedTokens['delete'];
} }
/**
* @deprecated since 1.24
*/
public static function getProtectToken( $pageid, $title ) { public static function getProtectToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isAllowed( 'protect' ) ) { if ( !$wgUser->isAllowed( 'protect' ) ) {
@ -159,6 +172,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['protect']; return ApiQueryInfo::$cachedTokens['protect'];
} }
/**
* @deprecated since 1.24
*/
public static function getMoveToken( $pageid, $title ) { public static function getMoveToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isAllowed( 'move' ) ) { if ( !$wgUser->isAllowed( 'move' ) ) {
@ -173,6 +189,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['move']; return ApiQueryInfo::$cachedTokens['move'];
} }
/**
* @deprecated since 1.24
*/
public static function getBlockToken( $pageid, $title ) { public static function getBlockToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isAllowed( 'block' ) ) { if ( !$wgUser->isAllowed( 'block' ) ) {
@ -187,11 +206,17 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['block']; return ApiQueryInfo::$cachedTokens['block'];
} }
/**
* @deprecated since 1.24
*/
public static function getUnblockToken( $pageid, $title ) { public static function getUnblockToken( $pageid, $title ) {
// Currently, this is exactly the same as the block token // Currently, this is exactly the same as the block token
return self::getBlockToken( $pageid, $title ); return self::getBlockToken( $pageid, $title );
} }
/**
* @deprecated since 1.24
*/
public static function getEmailToken( $pageid, $title ) { public static function getEmailToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) { if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailUser() ) {
@ -206,6 +231,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['email']; return ApiQueryInfo::$cachedTokens['email'];
} }
/**
* @deprecated since 1.24
*/
public static function getImportToken( $pageid, $title ) { public static function getImportToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) { if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
@ -220,6 +248,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['import']; return ApiQueryInfo::$cachedTokens['import'];
} }
/**
* @deprecated since 1.24
*/
public static function getWatchToken( $pageid, $title ) { public static function getWatchToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isLoggedIn() ) { if ( !$wgUser->isLoggedIn() ) {
@ -234,6 +265,9 @@ class ApiQueryInfo extends ApiQueryBase {
return ApiQueryInfo::$cachedTokens['watch']; return ApiQueryInfo::$cachedTokens['watch'];
} }
/**
* @deprecated since 1.24
*/
public static function getOptionsToken( $pageid, $title ) { public static function getOptionsToken( $pageid, $title ) {
global $wgUser; global $wgUser;
if ( !$wgUser->isLoggedIn() ) { if ( !$wgUser->isLoggedIn() ) {
@ -784,6 +818,7 @@ class ApiQueryInfo extends ApiQueryBase {
// need to be added to getCacheMode() // need to be added to getCacheMode()
) ), ) ),
'token' => array( 'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null, ApiBase::PARAM_DFLT => null,
ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_ISMULTI => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ) 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. * Get an array mapping token names to their handler functions.
* The prototype for a token function is func($pageid, $title, $rc) * The prototype for a token function is func($pageid, $title, $rc)
* it should return a token or false (permission denied) * it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array(tokenname => function) * @return array Array(tokenname => function)
*/ */
protected function getTokenFunctions() { protected function getTokenFunctions() {
@ -69,6 +70,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
} }
/** /**
* @deprecated since 1.24
* @param int $pageid * @param int $pageid
* @param Title $title * @param Title $title
* @param RecentChange|null $rc * @param RecentChange|null $rc
@ -657,6 +659,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
) )
), ),
'token' => array( 'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true ApiBase::PARAM_ISMULTI => true
), ),

View file

@ -48,6 +48,7 @@ class ApiQueryRevisions extends ApiQueryBase {
private $tokenFunctions; private $tokenFunctions;
/** @deprecated since 1.24 */
protected function getTokenFunctions() { protected function getTokenFunctions() {
// tokenname => function // tokenname => function
// function prototype is func($pageid, $title, $rev) // function prototype is func($pageid, $title, $rev)
@ -72,6 +73,7 @@ class ApiQueryRevisions extends ApiQueryBase {
} }
/** /**
* @deprecated since 1.24
* @param int $pageid * @param int $pageid
* @param Title $title * @param Title $title
* @param Revision $rev * @param Revision $rev
@ -748,6 +750,7 @@ class ApiQueryRevisions extends ApiQueryBase {
'parse' => false, 'parse' => false,
'section' => null, 'section' => null,
'token' => array( 'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true 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(); $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'] ) && if ( isset( $this->prop['preferencestoken'] ) &&
is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) && is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) &&
$user->isAllowed( 'editmyoptions' ) $user->isAllowed( 'editmyoptions' )
@ -252,7 +258,7 @@ class ApiQueryUserInfo extends ApiQueryBase {
' rights - Lists all the rights the current user has', ' rights - Lists all the rights the current user has',
' changeablegroups - Lists the groups the current user can add to and remove from', ' changeablegroups - Lists the groups the current user can add to and remove from',
' options - Lists all preferences the current user has set', ' 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', ' editcount - Adds the current user\'s edit count',
' ratelimits - Lists all rate limits applying to the current user', ' ratelimits - Lists all rate limits applying to the current user',
' realname - Adds the user\'s real name', ' 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. * Get an array mapping token names to their handler functions.
* The prototype for a token function is func($user) * The prototype for a token function is func($user)
* it should return a token or false (permission denied) * it should return a token or false (permission denied)
* @deprecated since 1.24
* @return array Array of tokenname => function * @return array Array of tokenname => function
*/ */
protected function getTokenFunctions() { protected function getTokenFunctions() {
@ -80,6 +81,7 @@ class ApiQueryUsers extends ApiQueryBase {
} }
/** /**
* @deprecated since 1.24
* @param User $user * @param User $user
* @return string * @return string
*/ */
@ -317,6 +319,7 @@ class ApiQueryUsers extends ApiQueryBase {
ApiBase::PARAM_ISMULTI => true ApiBase::PARAM_ISMULTI => true
), ),
'token' => array( 'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ), ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true ApiBase::PARAM_ISMULTI => true
), ),

View file

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

View file

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

View file

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

View file

@ -25,11 +25,16 @@
*/ */
/** /**
* @deprecated since 1.24
* @ingroup API * @ingroup API
*/ */
class ApiTokens extends ApiBase { class ApiTokens extends ApiBase {
public function execute() { public function execute() {
$this->setWarning(
"action=tokens has been deprecated. Please use action=query&meta=tokens instead."
);
$params = $this->extractRequestParams(); $params = $this->extractRequestParams();
$res = array(); $res = array();
@ -88,7 +93,10 @@ class ApiTokens extends ApiBase {
} }
public function getDescription() { 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() { protected function getExamples() {

View file

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

View file

@ -96,10 +96,6 @@ class ApiUndelete extends ApiBase {
ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true ApiBase::PARAM_REQUIRED => true
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => '', 'reason' => '',
'timestamps' => array( 'timestamps' => array(
ApiBase::PARAM_TYPE => 'timestamp', ApiBase::PARAM_TYPE => 'timestamp',
@ -124,10 +120,6 @@ class ApiUndelete extends ApiBase {
public function getParamDescription() { public function getParamDescription() {
return array( return array(
'title' => 'Title of the page you want to restore', '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', 'reason' => 'Reason for restoring',
'timestamps' => array( 'timestamps' => array(
'Timestamps of the revisions to restore.', 'Timestamps of the revisions to restore.',
@ -151,11 +143,7 @@ class ApiUndelete extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {

View file

@ -688,10 +688,6 @@ class ApiUpload extends ApiBase {
ApiBase::PARAM_DFLT => '' ApiBase::PARAM_DFLT => ''
), ),
'text' => null, 'text' => null,
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'watch' => array( 'watch' => array(
ApiBase::PARAM_DFLT => false, ApiBase::PARAM_DFLT => false,
ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_DEPRECATED => true,
@ -735,7 +731,6 @@ class ApiUpload extends ApiBase {
public function getParamDescription() { public function getParamDescription() {
$params = array( $params = array(
'filename' => 'Target filename', '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 ' . 'comment' => 'Upload comment. Also used as the initial page text for new ' .
'files if "text" is not specified', 'files if "text" is not specified',
'text' => 'Initial page text for new files', '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', ' * 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', ' * 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', '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() { public function needsToken() {
return true; return 'csrf';
}
public function getTokenSalt() {
return '';
} }
public function getExamples() { public function getExamples() {
return array( return array(
'api.php?action=upload&filename=Wiki.png' . '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', => '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', => 'Complete an upload that failed due to warnings',
); );
} }

View file

@ -35,7 +35,7 @@ class ApiUserrights extends ApiBase {
public function execute() { public function execute() {
$params = $this->extractRequestParams(); $params = $this->extractRequestParams();
$user = $this->getUrUser(); $user = $this->getUrUser( $params );
$form = new UserrightsPage; $form = new UserrightsPage;
$form->setContext( $this->getContext() ); $form->setContext( $this->getContext() );
@ -53,14 +53,14 @@ class ApiUserrights extends ApiBase {
} }
/** /**
* @param array $params
* @return User * @return User
*/ */
private function getUrUser() { private function getUrUser( array $params ) {
if ( $this->mUser !== null ) { if ( $this->mUser !== null ) {
return $this->mUser; return $this->mUser;
} }
$params = $this->extractRequestParams();
$this->requireOnlyOneParameter( $params, 'user', 'userid' ); $this->requireOnlyOneParameter( $params, 'user', 'userid' );
$user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid']; $user = isset( $params['user'] ) ? $params['user'] : '#' . $params['userid'];
@ -101,10 +101,6 @@ class ApiUserrights extends ApiBase {
ApiBase::PARAM_TYPE => User::getAllGroups(), ApiBase::PARAM_TYPE => User::getAllGroups(),
ApiBase::PARAM_ISMULTI => true ApiBase::PARAM_ISMULTI => true
), ),
'token' => array(
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
'reason' => array( 'reason' => array(
ApiBase::PARAM_DFLT => '' ApiBase::PARAM_DFLT => ''
) )
@ -117,7 +113,10 @@ class ApiUserrights extends ApiBase {
'userid' => 'User id', 'userid' => 'User id',
'add' => 'Add the user to these groups', 'add' => 'Add the user to these groups',
'remove' => 'Remove the user from 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', 'reason' => 'Reason for the change',
); );
} }
@ -127,11 +126,11 @@ class ApiUserrights extends ApiBase {
} }
public function needsToken() { public function needsToken() {
return true; return 'userrights';
} }
public function getTokenSalt() { protected function getWebUITokenSalt( array $params ) {
return $this->getUrUser()->getName(); return $this->getUrUser( $params )->getName();
} }
public function getExamples() { public function getExamples() {

View file

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

View file

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