From 298cf413dbc3fa4ee750758e7272ca401da4862c Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Wed, 15 Oct 2014 20:48:35 +0000 Subject: [PATCH] mediawiki.api: Use action=query&meta=tokens instead of action=tokens Follows-up aacdb664a1, which was reverted. API action=query&meta=tokens has different token types than the old action=tokens values. Use a map to maintain support in the JavaScript API for old token types that now fold into the generic 'csrf'. Aside from core token types, those added by extensions are no longer actively used from the old token API. Bug: T72059 Change-Id: Iec3a9f0f51d64d90c81a147cc18097dcf679c7c9 --- .../ResourceLoaderUserTokensModule.php | 1 + resources/src/mediawiki/api.js | 51 +++++++++++++------ .../mediawiki.api.options.test.js | 6 +-- .../mediawiki.api/mediawiki.api.test.js | 39 ++++++++++---- 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/includes/resourceloader/ResourceLoaderUserTokensModule.php b/includes/resourceloader/ResourceLoaderUserTokensModule.php index d37aa55750..78fec5094a 100644 --- a/includes/resourceloader/ResourceLoaderUserTokensModule.php +++ b/includes/resourceloader/ResourceLoaderUserTokensModule.php @@ -47,6 +47,7 @@ class ResourceLoaderUserTokensModule extends ResourceLoaderModule { 'editToken' => $user->getEditToken(), 'patrolToken' => $user->getEditToken( 'patrol' ), 'watchToken' => $user->getEditToken( 'watch' ), + 'csrfToken' => $user->getEditToken(), ); } diff --git a/resources/src/mediawiki/api.js b/resources/src/mediawiki/api.js index 5f82b18827..c26dd6ae12 100644 --- a/resources/src/mediawiki/api.js +++ b/resources/src/mediawiki/api.js @@ -26,6 +26,22 @@ // Keyed by ajax url and symbolic name for the individual request promises = {}; + function mapLegacyToken( action ) { + // Legacy types for backward-compatibility with API action=tokens. + var csrfActions = [ + 'edit', + 'delete', + 'protect', + 'move', + 'block', + 'unblock', + 'email', + 'import', + 'options' + ]; + return $.inArray( action, csrfActions ) !== -1 ? 'csrf' : action; + } + // Pre-populate with fake ajax promises to save http requests for tokens // we already have on the page via the user.tokens module (bug 34733). promises[ defaultOptions.ajax.url ] = {}; @@ -293,34 +309,37 @@ /** * Get a token for a certain action from the API. * - * The assert parameter is only for internal use by postWithToken. + * The assert parameter is only for internal use by #postWithToken. * - * @param {string} type Token type - * @return {jQuery.Promise} - * @return {Function} return.done - * @return {string} return.done.token Received token. * @since 1.22 + * @param {string} type Token type + * @return {jQuery.Promise} Received token. */ getToken: function ( type, assert ) { - var apiPromise, - promiseGroup = promises[ this.defaults.ajax.url ], - d = promiseGroup && promiseGroup[ type + 'Token' ]; + var apiPromise, promiseGroup, d; + type = mapLegacyToken( type ); + promiseGroup = promises[ this.defaults.ajax.url ]; + d = promiseGroup && promiseGroup[ type + 'Token' ]; if ( !d ) { - apiPromise = this.get( { action: 'tokens', type: type, assert: assert } ); - + apiPromise = this.get( { + action: 'query', + meta: 'tokens', + type: type, + assert: assert + } ); d = apiPromise - .then( function ( data ) { - if ( data.tokens && data.tokens[ type + 'token' ] ) { - return data.tokens[ type + 'token' ]; + .then( function ( res ) { + // If token type is unknown, it is omitted from the response + if ( !res.query.tokens[ type + 'token' ] ) { + return $.Deferred().reject( 'token-missing', res ); } - // If token type is not available for this user, - // key '...token' is either missing or set to boolean false - return $.Deferred().reject( 'token-missing', data ); + return res.query.tokens[ type + 'token' ]; }, function () { // Clear promise. Do not cache errors. delete promiseGroup[ type + 'Token' ]; + // Pass on to allow the caller to handle the error return this; } ) diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js index c0a6585f30..a0cfba9848 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.options.test.js @@ -27,9 +27,9 @@ // until after the server.respond call, which confuses sinon terribly. This sucks a lot. api.getToken( 'options' ); this.server.respond( - /action=tokens.*&type=options/, + /meta=tokens&type=csrf/, [ 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "optionstoken": "+\\\\" } }' ] + '{ "query": { "tokens": { "csrftoken": "+\\\\" } } }' ] ); api.saveOptions( {} ).done( function () { @@ -71,7 +71,7 @@ '{ "options": "success" }' ); break; default: - assert.ok( false, 'Unexpected request:' + request.requestBody ); + assert.ok( false, 'Unexpected request: ' + request.requestBody ); } } ); } ); diff --git a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js index 56a346fb5e..a34a5af996 100644 --- a/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js +++ b/tests/qunit/suites/resources/mediawiki.api/mediawiki.api.test.js @@ -125,7 +125,7 @@ var api = new mw.Api(); this.server.respondWith( /type=testuncached/, [ 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testuncachedtoken": "good" } }' + '{ "query": { "tokens": { "testuncachedtoken": "good" } } }' ] ); // Get a token of a type that isn't prepopulated by user.tokens. @@ -157,7 +157,7 @@ this.server.respondWith( /type=testerror/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, [ '{ "error": { "code": "bite-me", "info": "Smite me, O Mighty Smiter" } }', - '{ "tokens": { "testerrortoken": "good" } }' + '{ "query": { "tokens": { "testerrortoken": "good" } } }' ] ) ); @@ -175,6 +175,27 @@ } ); } ); + QUnit.test( 'getToken() - deprecated', function ( assert ) { + QUnit.expect( 2 ); + // Cache API endpoint from default to avoid cachehit in mw.user.tokens + var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); + + this.server.respondWith( /type=csrf/, [ 200, { 'Content-Type': 'application/json' }, + '{ "query": { "tokens": { "csrftoken": "csrfgood" } } }' + ] ); + + // Get a token of a type that is in the legacy map. + api.getToken( 'email' ) + .done( function ( token ) { + assert.equal( token, 'csrfgood', 'Token' ); + } ) + .fail( function ( err ) { + assert.equal( err, '', 'API error' ); + } ); + + assert.equal( this.server.requests.length, 1, 'Requests made' ); + } ); + QUnit.test( 'badToken()', function ( assert ) { QUnit.expect( 2 ); var api = new mw.Api(), @@ -182,8 +203,8 @@ this.server.respondWith( /type=testbad/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, [ - '{ "tokens": { "testbadtoken": "bad" } }', - '{ "tokens": { "testbadtoken": "good" } }' + '{ "query": { "tokens": { "testbadtoken": "bad" } } }', + '{ "query": { "tokens": { "testbadtoken": "good" } } }' ] ) ); @@ -204,7 +225,7 @@ var api = new mw.Api( { ajax: { url: '/postWithToken/api.php' } } ); this.server.respondWith( 'GET', /type=testpost/, [ 200, { 'Content-Type': 'application/json' }, - '{ "tokens": { "testposttoken": "good" } }' + '{ "query": { "tokens": { "testposttoken": "good" } } }' ] ); this.server.respondWith( 'POST', /api/, function ( request ) { if ( request.requestBody.match( /token=good/ ) ) { @@ -277,8 +298,8 @@ this.server.respondWith( /type=testbadtoken/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, [ - '{ "tokens": { "testbadtokentoken": "bad" } }', - '{ "tokens": { "testbadtokentoken": "good" } }' + '{ "query": { "tokens": { "testbadtokentoken": "bad" } } }', + '{ "query": { "tokens": { "testbadtokentoken": "good" } } }' ] ) ); this.server.respondWith( 'POST', /api/, function ( request ) { @@ -311,8 +332,8 @@ this.server.respondWith( /type=testonce/, sequenceBodies( 200, { 'Content-Type': 'application/json' }, [ - '{ "tokens": { "testoncetoken": "good-A" } }', - '{ "tokens": { "testoncetoken": "good-B" } }' + '{ "query": { "tokens": { "testoncetoken": "good-A" } } }', + '{ "query": { "tokens": { "testoncetoken": "good-B" } } }' ] ) ); sequenceA = sequenceBodies( 200, { 'Content-Type': 'application/json' }, -- 2.20.1