// 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 ] = {};
/**
* 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;
} )
// 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 () {
'{ "options": "success" }' );
break;
default:
- assert.ok( false, 'Unexpected request:' + request.requestBody );
+ assert.ok( false, 'Unexpected request: ' + request.requestBody );
}
} );
} );
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.
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" } } }'
]
) );
} );
} );
+ 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(),
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" } } }'
]
) );
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/ ) ) {
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 ) {
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' },