* (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
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 ===
&$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
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
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.
'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',
*/
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;
*/
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;
* @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
*
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => null,
'expiry' => 'never',
'reason' => '',
'anononly' => false,
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',
}
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'
);
}
'pageid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => null,
'watch' => array(
ApiBase::PARAM_DFLT => false,
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',
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
ApiBase::PARAM_TYPE => 'string',
),
'text' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'minor' => false,
'notminor' => false,
'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'
'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',
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'ccme' => false,
);
}
'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',
);
}
}
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"',
);
}
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',
);
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
ApiBase::PARAM_TYPE => array( '90', '180', '270' ),
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'continue' => '',
);
if ( $flags ) {
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',
);
}
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
public function getAllowedParams() {
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => null,
'xml' => array(
ApiBase::PARAM_TYPE => 'upload',
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',
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
}
}
+ if ( $this->getParameter( 'curtimestamp' ) ) {
+ $result->addValue( null, 'curtimestamp', wfTimestamp( TS_ISO_8601, time() ),
+ ApiResult::NO_SIZE_CHECK );
+ }
+
$params = $this->extractRequestParams();
$this->mAction = $params['action'];
}
$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' );
}
}
),
'requestid' => null,
'servedby' => false,
+ 'curtimestamp' => false,
'origin' => null,
);
}
'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',
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => '',
'movetalk' => false,
'movesubpages' => false,
'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',
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
$optionKinds[] = 'all';
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reset' => false,
'resetkinds' => array(
ApiBase::PARAM_TYPE => $optionKinds,
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), ' .
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getHelpUrls() {
$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' ) {
public function getAllowedParams() {
return array(
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'rcid' => array(
ApiBase::PARAM_TYPE => 'integer'
),
public function getParamDescription() {
return array(
- 'token' => 'Patrol token obtained from list=recentchanges',
'rcid' => 'Recentchanges ID to patrol',
'revid' => 'Revision ID to patrol',
);
}
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'
);
}
'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,
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 ' .
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
'siteinfo' => 'ApiQuerySiteinfo',
'userinfo' => 'ApiQueryUserInfo',
'filerepoinfo' => 'ApiQueryFileRepoInfo',
+ 'tokens' => 'ApiQueryTokens',
);
/**
$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;
' 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)',
* 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() {
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
return ApiQueryInfo::$cachedTokens['edit'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getDeleteToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'delete' ) ) {
return ApiQueryInfo::$cachedTokens['delete'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getProtectToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'protect' ) ) {
return ApiQueryInfo::$cachedTokens['protect'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getMoveToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'move' ) ) {
return ApiQueryInfo::$cachedTokens['move'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getBlockToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowed( 'block' ) ) {
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() ) {
return ApiQueryInfo::$cachedTokens['email'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getImportToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) {
return ApiQueryInfo::$cachedTokens['import'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getWatchToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
return ApiQueryInfo::$cachedTokens['watch'];
}
+ /**
+ * @deprecated since 1.24
+ */
public static function getOptionsToken( $pageid, $title ) {
global $wgUser;
if ( !$wgUser->isLoggedIn() ) {
// 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() )
* 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() {
}
/**
+ * @deprecated since 1.24
* @param int $pageid
* @param Title $title
* @param RecentChange|null $rc
)
),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
private $tokenFunctions;
+ /** @deprecated since 1.24 */
protected function getTokenFunctions() {
// tokenname => function
// function prototype is func($pageid, $title, $rev)
}
/**
+ * @deprecated since 1.24
* @param int $pageid
* @param Title $title
* @param Revision $rev
'parse' => false,
'section' => null,
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
--- /dev/null
+<?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';
+ }
+}
$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' )
' 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',
* 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() {
}
/**
+ * @deprecated since 1.24
* @param User $user
* @return string
*/
ApiBase::PARAM_ISMULTI => true
),
'token' => array(
+ ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() ),
ApiBase::PARAM_ISMULTI => true
),
ApiBase::PARAM_TYPE => array( 'yes', 'no', 'nochange' ),
ApiBase::PARAM_DFLT => 'nochange',
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => null,
);
}
'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',
);
}
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
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'];
$retval = $pageObj->doRollback(
$this->getRbUser( $params ),
$summary,
- $params['token'],
+ $token,
$params['markbot'],
$details,
- $this->getUser()
+ $user
);
if ( $retval ) {
ApiBase::PARAM_TYPE => 'string',
ApiBase::PARAM_REQUIRED => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'summary' => '',
'markbot' => false,
'watchlist' => array(
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, ' .
}
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 )
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getAllowedParams( $flags = 0 ) {
'entirewatchlist' => array(
ApiBase::PARAM_TYPE => 'boolean'
),
- 'token' => null,
'timestamp' => array(
ApiBase::PARAM_TYPE => 'timestamp'
),
'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',
);
}
*/
/**
+ * @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();
}
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() {
ApiBase::PARAM_TYPE => 'integer',
),
'user' => null,
- 'token' => null,
'reason' => '',
);
}
"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',
);
}
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
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',
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.',
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
- return '';
+ return 'csrf';
}
public function getExamples() {
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,
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',
' * 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',
);
}
public function execute() {
$params = $this->extractRequestParams();
- $user = $this->getUrUser();
+ $user = $this->getUrUser( $params );
$form = new UserrightsPage;
$form->setContext( $this->getContext() );
}
/**
+ * @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'];
ApiBase::PARAM_TYPE => User::getAllGroups(),
ApiBase::PARAM_ISMULTI => true
),
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'reason' => array(
ApiBase::PARAM_DFLT => ''
)
'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',
);
}
}
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() {
}
public function needsToken() {
- return true;
- }
-
- public function getTokenSalt() {
return 'watch';
}
),
'unwatch' => false,
'uselang' => null,
- 'token' => array(
- ApiBase::PARAM_TYPE => 'string',
- ApiBase::PARAM_REQUIRED => true
- ),
'continue' => '',
);
if ( $flags ) {
'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',
);
}
return array(
'editToken' => $wgUser->getEditToken(),
- 'patrolToken' => ApiQueryRecentChanges::getPatrolToken( null, null ),
- 'watchToken' => ApiQueryInfo::getWatchToken( null, null ),
+ 'patrolToken' => $wgUser->getEditToken( 'patrol' ),
+ 'watchToken' => $wgUser->getEditToken( 'watch' ),
);
}