* list=tags has additional properties to indicate 'active' status and tag
sources.
* siprop=libraries was added to ApiQuerySiteInfo to list installed external libraries.
+* (T88010) Added action=checktoken, to test a CSRF token's validity.
+* (T88010) Added intestactions to prop=info, to allow querying of
+ Title::userCan() via the API.
=== Action API internal changes in 1.25 ===
* ApiHelp has been rewritten to support i18n and paginated HTML output.
'AnsiTermColorer' => __DIR__ . '/maintenance/term/MWTerm.php',
'ApiBase' => __DIR__ . '/includes/api/ApiBase.php',
'ApiBlock' => __DIR__ . '/includes/api/ApiBlock.php',
+ 'ApiCheckToken' => __DIR__ . '/includes/api/ApiCheckToken.php',
'ApiClearHasMsg' => __DIR__ . '/includes/api/ApiClearHasMsg.php',
'ApiComparePages' => __DIR__ . '/includes/api/ApiComparePages.php',
'ApiCreateAccount' => __DIR__ . '/includes/api/ApiCreateAccount.php',
return MWCryptRand::generateHex( 32 );
}
+ /**
+ * Get the embedded timestamp from a token.
+ * @param string $val Input token
+ * @return int|null
+ */
+ public static function getEditTokenTimestamp( $val ) {
+ $suffixLen = strlen( self::EDIT_TOKEN_SUFFIX );
+ if ( strlen( $val ) <= 32 + $suffixLen ) {
+ return null;
+ }
+
+ return hexdec( substr( $val, 32, -$suffixLen ) );
+ }
+
/**
* Check given value against the token value stored in the session.
* A match should confirm that the form was submitted from the
return $val === self::EDIT_TOKEN_SUFFIX;
}
- $suffixLen = strlen( self::EDIT_TOKEN_SUFFIX );
- if ( strlen( $val ) <= 32 + $suffixLen ) {
+ $timestamp = self::getEditTokenTimestamp( $val );
+ if ( $timestamp === null ) {
return false;
}
-
- $timestamp = hexdec( substr( $val, 32, -$suffixLen ) );
if ( $maxage !== null && $timestamp < wfTimestamp() - $maxage ) {
// Expired token
return false;
--- /dev/null
+<?php
+/**
+ * Created on Jan 29, 2015
+ *
+ * Copyright © 2015 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.25
+ * @ingroup API
+ */
+class ApiCheckToken extends ApiBase {
+
+ public function execute() {
+ $params = $this->extractRequestParams();
+ $token = $params['token'];
+ $maxage = $params['maxtokenage'];
+ $request = $this->getRequest();
+ $salts = ApiQueryTokens::getTokenTypeSalts();
+ $salt = $salts[$params['type']];
+
+ $res = array();
+
+ if ( $this->getUser()->matchEditToken( $token, $salt, $request, $maxage ) ) {
+ $res['result'] = 'valid';
+ } elseif ( $maxage !== null && $this->getUser()->matchEditToken( $token, $salt, $request ) ) {
+ $res['result'] = 'expired';
+ } else {
+ $res['result'] = 'invalid';
+ }
+
+ $ts = User::getEditTokenTimestamp( $token );
+ if ( $ts !== null ) {
+ $mwts = new MWTimestamp();
+ $mwts->timestamp->setTimestamp( $ts );
+ $res['generated'] = $mwts->getTimestamp( TS_ISO_8601 );
+ }
+
+ $this->getResult()->addValue( null, $this->getModuleName(), $res );
+ }
+
+ public function getAllowedParams() {
+ return array(
+ 'type' => array(
+ ApiBase::PARAM_TYPE => array_keys( ApiQueryTokens::getTokenTypeSalts() ),
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'token' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_REQUIRED => true,
+ ),
+ 'maxtokenage' => array(
+ ApiBase::PARAM_TYPE => 'integer',
+ ),
+ );
+ }
+
+ protected function getExamplesMessages() {
+ return array(
+ 'action=checktoken&type=csrf&token=123ABC'
+ => 'apihelp-checktoken-example-simple',
+ );
+ }
+}
'rsd' => 'ApiRsd',
'compare' => 'ApiComparePages',
'tokens' => 'ApiTokens',
+ 'checktoken' => 'ApiCheckToken',
// Write modules
'purge' => 'ApiPurge',
private $tokenFunctions;
+ private $countTestedActions = 0;
+
public function __construct( ApiQuery $query, $moduleName ) {
parent::__construct( $query, $moduleName, 'in' );
}
/** @var $title Title */
foreach ( $this->everything as $pageid => $title ) {
$pageInfo = $this->extractPageInfo( $pageid, $title );
- $fit = $result->addValue( array(
+ $fit = $pageInfo !== null && $result->addValue( array(
'query',
'pages'
), $pageid, $pageInfo );
* Get a result array with information about a title
* @param int $pageid Page ID (negative for missing titles)
* @param Title $title
- * @return array
+ * @return array|null
*/
private function extractPageInfo( $pageid, $title ) {
$pageInfo = array();
}
}
+ if ( $this->params['testactions'] ) {
+ $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML1 : self::LIMIT_SML2;
+ if ( $this->countTestedActions >= $limit ) {
+ return null; // force a continuation
+ }
+
+ $user = $this->getUser();
+ $pageInfo['actions'] = array();
+ foreach ( $this->params['testactions'] as $action ) {
+ $this->countTestedActions++;
+ if ( $title->userCan( $action, $user ) ) {
+ $pageInfo['actions'][$action] = '';
+ }
+ }
+ }
+
return $pageInfo;
}
),
ApiBase::PARAM_HELP_MSG_PER_VALUE => array(),
),
+ 'testactions' => array(
+ ApiBase::PARAM_TYPE => 'string',
+ ApiBase::PARAM_ISMULTI => true,
+ ),
'token' => array(
ApiBase::PARAM_DEPRECATED => true,
ApiBase::PARAM_DFLT => null,
"apihelp-block-example-ip-simple": "Block IP address <kbd>192.0.2.5</kbd> for three days with reason <kbd>First strike</kbd>.",
"apihelp-block-example-user-complex": "Block user <kbd>Vandal</kbd> indefinitely with reason <kbd>Vandalism</kbd>, and prevent new account creation and email sending.",
+ "apihelp-checktoken-description": "Check the validity of a token from <kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd>.",
+ "apihelp-checktoken-param-type": "Type of token being tested.",
+ "apihelp-checktoken-param-token": "Token to test.",
+ "apihelp-checktoken-param-maxtokenage": "Maximum allowed age of the token, in seconds.",
+ "apihelp-checktoken-example-simple": "Test the validity of a <kbd>csrf</kbd> token.",
+
"apihelp-clearhasmsg-description": "Clears the <code>hasmsg</code> flag for the current user.",
"apihelp-clearhasmsg-example-1": "Clear the <code>hasmsg</code> flag for the current user.",
"apihelp-query+info-paramvalue-prop-readable": "Whether the user can read this page.",
"apihelp-query+info-paramvalue-prop-preload": "Gives the text returned by EditFormPreloadText.",
"apihelp-query+info-paramvalue-prop-displaytitle": "Gives the way the page title is actually displayed.",
+ "apihelp-query+info-param-testactions": "Test whether the current user can perform certain actions on the page.",
"apihelp-query+info-param-token": "Use [[Special:ApiHelp/query+tokens|action=query&meta=tokens]] instead.",
"apihelp-query+info-example-simple": "Get information about the page <kbd>Main Page</kbd>.",
"apihelp-query+info-example-protection": "Get general and protection information about the page <kbd>Main Page</kbd>.",
"apihelp-block-param-watchuser": "{{doc-apihelp-param|block|watchuser}}",
"apihelp-block-example-ip-simple": "{{doc-apihelp-example|block}}",
"apihelp-block-example-user-complex": "{{doc-apihelp-example|block}}",
+ "apihelp-checktoken-description": "{{doc-apihelp-description|checktoken}}",
+ "apihelp-checktoken-param-type": "{{doc-apihelp-param|checktoken|type}}",
+ "apihelp-checktoken-param-token": "{{doc-apihelp-param|checktoken|token}}",
+ "apihelp-checktoken-param-maxtokenage": "{{doc-apihelp-param|checktoken|maxtokenage}}",
+ "apihelp-checktoken-example-simple": "{{doc-apihelp-example|checktoken}}",
"apihelp-clearhasmsg-description": "{{doc-apihelp-description|clear<code>hasmsg</code>}}",
"apihelp-clearhasmsg-example-1": "{{doc-apihelp-example|clear<code>hasmsg</code>}}",
"apihelp-compare-description": "{{doc-apihelp-description|compare}}",
"apihelp-query+info-paramvalue-prop-readable": "{{doc-apihelp-paramvalue|query+info|prop|readable}}",
"apihelp-query+info-paramvalue-prop-preload": "{{doc-apihelp-paramvalue|query+info|prop|preload}}",
"apihelp-query+info-paramvalue-prop-displaytitle": "{{doc-apihelp-paramvalue|query+info|prop|displaytitle}}",
+ "apihelp-query+info-param-testactions": "{{doc-apihelp-param|query+info|testactions}}",
"apihelp-query+info-param-token": "{{doc-apihelp-param|query+info|token}}",
"apihelp-query+info-example-simple": "{{doc-apihelp-example|query+info}}",
"apihelp-query+info-example-protection": "{{doc-apihelp-example|query+info}}",