Introduce mediawiki.ForeignApi
mw.ForeignApi is an extension of mw.Api, automatically handling everything required to communicate with another MediaWiki wiki via cross-origin requests (CORS). Authentication-related MediaWiki extensions may extend it further to ensure that the user authenticated on the current wiki will be automatically authenticated on the foreign one. A CentralAuth implementation is provided in I0fd05ef8b9c9db0fdb59c6cb248f364259f80456. Bug: T66636 Change-Id: Ic20b9682d28633baa87d22e6e9fb71ce507da58d
This commit is contained in:
parent
a2f75a52ef
commit
2f30ff7a86
8 changed files with 204 additions and 1 deletions
|
|
@ -1012,6 +1012,7 @@ $wgAutoloadLocalClasses = array(
|
|||
'ResourceLoaderEditToolbarModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderEditToolbarModule.php',
|
||||
'ResourceLoaderFileModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderFileModule.php',
|
||||
'ResourceLoaderFilePath' => __DIR__ . '/includes/resourceloader/ResourceLoaderFilePath.php',
|
||||
'ResourceLoaderForeignApiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderForeignApiModule.php',
|
||||
'ResourceLoaderImage' => __DIR__ . '/includes/resourceloader/ResourceLoaderImage.php',
|
||||
'ResourceLoaderImageModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderImageModule.php',
|
||||
'ResourceLoaderJqueryMsgModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderJqueryMsgModule.php',
|
||||
|
|
|
|||
|
|
@ -2448,6 +2448,13 @@ $user: The user having their password expiration reset
|
|||
$oldSessionID: old session id
|
||||
$newSessionID: new session id
|
||||
|
||||
'ResourceLoaderForeignApiModules': Called from ResourceLoaderForeignApiModule.
|
||||
Use this to add dependencies to 'mediawiki.ForeignApi' module when you wish
|
||||
to override its behavior. See the module docs for more information.
|
||||
&$dependencies: string[] List of modules that 'mediawiki.ForeignApi' should
|
||||
depend on
|
||||
$context: ResourceLoaderContext|null
|
||||
|
||||
'ResourceLoaderGetConfigVars': Called at the end of
|
||||
ResourceLoaderStartUpModule::getConfigSettings(). Use this to export static
|
||||
configuration variables to JavaScript. Things that depend on the current page
|
||||
|
|
|
|||
33
includes/resourceloader/ResourceLoaderForeignApiModule.php
Normal file
33
includes/resourceloader/ResourceLoaderForeignApiModule.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/**
|
||||
* ResourceLoader module for mediawiki.ForeignApi that has dynamically
|
||||
* generated dependencies, via a hook usable by extensions.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* ResourceLoader module for mediawiki.ForeignApi and its generated data
|
||||
*/
|
||||
class ResourceLoaderForeignApiModule extends ResourceLoaderFileModule {
|
||||
public function getDependencies( ResourceLoaderContext $context = null ) {
|
||||
$dependencies = $this->dependencies;
|
||||
Hooks::run( 'ResourceLoaderForeignApiModules', array( &$dependencies, $context ) );
|
||||
return $dependencies;
|
||||
}
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
},
|
||||
{
|
||||
"name": "API",
|
||||
"classes": ["mw.Api*"]
|
||||
"classes": ["mw.Api*", "mw.ForeignApi*"]
|
||||
},
|
||||
{
|
||||
"name": "Language",
|
||||
|
|
|
|||
|
|
@ -981,6 +981,18 @@ return array(
|
|||
'oojs-ui',
|
||||
),
|
||||
),
|
||||
'mediawiki.ForeignApi' => array(
|
||||
'class' => 'ResourceLoaderForeignApiModule',
|
||||
// Additional dependencies generated dynamically
|
||||
'dependencies' => 'mediawiki.ForeignApi.core',
|
||||
),
|
||||
'mediawiki.ForeignApi.core' => array(
|
||||
'scripts' => 'resources/src/mediawiki.api/mediawiki.ForeignApi.js',
|
||||
'dependencies' => array(
|
||||
'mediawiki.api',
|
||||
'oojs',
|
||||
),
|
||||
),
|
||||
'mediawiki.helplink' => array(
|
||||
'position' => 'top',
|
||||
'styles' => array(
|
||||
|
|
|
|||
109
resources/src/mediawiki.api/mediawiki.ForeignApi.js
Normal file
109
resources/src/mediawiki.api/mediawiki.ForeignApi.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
( function ( mw, $ ) {
|
||||
|
||||
/**
|
||||
* Create an object like mw.Api, but automatically handling everything required to communicate
|
||||
* with another MediaWiki wiki via cross-origin requests (CORS).
|
||||
*
|
||||
* The foreign wiki must be configured to accept requests from the current wiki. See
|
||||
* <https://www.mediawiki.org/wiki/Manual:$wgCrossSiteAJAXdomains> for details.
|
||||
*
|
||||
* var api = new mw.ForeignApi( 'https://commons.wikimedia.org/w/api.php' );
|
||||
* api.get( {
|
||||
* action: 'query',
|
||||
* meta: 'userinfo'
|
||||
* } ).done( function ( data ) {
|
||||
* console.log( data );
|
||||
* } );
|
||||
*
|
||||
* To ensure that the user at the foreign wiki is logged in, pass the `assert: 'user'` parameter
|
||||
* to #get/#post (since MW 1.23): if they are not, the API request will fail. (Note that this
|
||||
* doesn't guarantee that it's the same user.)
|
||||
*
|
||||
* Authentication-related MediaWiki extensions may extend this class to ensure that the user
|
||||
* authenticated on the current wiki will be automatically authenticated on the foreign one. These
|
||||
* extension modules should be registered using the ResourceLoaderForeignApiModules hook. See
|
||||
* CentralAuth for a practical example. The general pattern to extend and override the name is:
|
||||
*
|
||||
* function MyForeignApi() {};
|
||||
* OO.inheritClass( MyForeignApi, mw.ForeignApi );
|
||||
* mw.ForeignApi = MyForeignApi;
|
||||
*
|
||||
* @class mw.ForeignApi
|
||||
* @extends mw.Api
|
||||
* @since 1.26
|
||||
*
|
||||
* @constructor
|
||||
* @param {string|mw.Uri} url URL pointing to another wiki's `api.php` endpoint.
|
||||
* @param {Object} [options] See mw.Api.
|
||||
*
|
||||
* @author Bartosz Dziewoński
|
||||
* @author Jon Robson
|
||||
*/
|
||||
function CoreForeignApi( url, options ) {
|
||||
if ( !url || $.isPlainObject( url ) ) {
|
||||
throw new Error( 'mw.ForeignApi() requires a `url` parameter' );
|
||||
}
|
||||
|
||||
this.apiUrl = String( url );
|
||||
|
||||
options = $.extend( /*deep=*/ true,
|
||||
{
|
||||
ajax: {
|
||||
url: this.apiUrl,
|
||||
xhrFields: {
|
||||
withCredentials: true
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
// Add 'origin' query parameter to all requests.
|
||||
origin: this.getOrigin()
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
// Call parent constructor
|
||||
CoreForeignApi.parent.call( this, options );
|
||||
}
|
||||
|
||||
OO.inheritClass( CoreForeignApi, mw.Api );
|
||||
|
||||
/**
|
||||
* Return the origin to use for API requests, in the required format (protocol, host and port, if
|
||||
* any).
|
||||
*
|
||||
* @protected
|
||||
* @return {string}
|
||||
*/
|
||||
CoreForeignApi.prototype.getOrigin = function () {
|
||||
var origin = window.location.protocol + '//' + window.location.hostname;
|
||||
if ( window.location.port ) {
|
||||
origin += ':' + window.location.port;
|
||||
}
|
||||
return origin;
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
CoreForeignApi.prototype.ajax = function ( parameters, ajaxOptions ) {
|
||||
var url, origin, newAjaxOptions;
|
||||
|
||||
// 'origin' query parameter must be part of the request URI, and not just POST request body
|
||||
if ( ajaxOptions.type !== 'GET' ) {
|
||||
url = ( ajaxOptions && ajaxOptions.url ) || this.defaults.ajax.url;
|
||||
origin = ( parameters && parameters.origin ) || this.defaults.parameters.origin;
|
||||
url += ( url.indexOf( '?' ) !== -1 ? '&' : '?' ) +
|
||||
'origin=' + encodeURIComponent( origin );
|
||||
newAjaxOptions = $.extend( {}, ajaxOptions, { url: url } );
|
||||
} else {
|
||||
newAjaxOptions = ajaxOptions;
|
||||
}
|
||||
|
||||
return CoreForeignApi.parent.prototype.ajax.call( this, parameters, newAjaxOptions );
|
||||
};
|
||||
|
||||
// Expose
|
||||
mw.ForeignApi = CoreForeignApi;
|
||||
|
||||
}( mediaWiki, jQuery ) );
|
||||
|
|
@ -83,6 +83,7 @@ return array(
|
|||
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.parse.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.upload.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.api/mediawiki.api.watch.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.api/mediawiki.ForeignApi.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki.special/mediawiki.special.recentchanges.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki/mediawiki.language.test.js',
|
||||
'tests/qunit/suites/resources/mediawiki/mediawiki.cldr.test.js',
|
||||
|
|
@ -111,6 +112,7 @@ return array(
|
|||
'mediawiki.api.parse',
|
||||
'mediawiki.api.upload',
|
||||
'mediawiki.api.watch',
|
||||
'mediawiki.ForeignApi.core',
|
||||
'mediawiki.jqueryMsg',
|
||||
'mediawiki.messagePoster',
|
||||
'mediawiki.RegExp',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
( function ( mw ) {
|
||||
QUnit.module( 'mediawiki.ForeignApi', QUnit.newMwEnvironment( {
|
||||
setup: function () {
|
||||
this.server = this.sandbox.useFakeServer();
|
||||
this.server.respondImmediately = true;
|
||||
this.clock = this.sandbox.useFakeTimers();
|
||||
},
|
||||
teardown: function () {
|
||||
// https://github.com/jquery/jquery/issues/2453
|
||||
this.clock.tick();
|
||||
}
|
||||
} ) );
|
||||
|
||||
QUnit.test( 'origin is included in GET requests', function ( assert ) {
|
||||
QUnit.expect( 1 );
|
||||
var api = new mw.ForeignApi( '//localhost:4242/w/api.php' );
|
||||
|
||||
this.server.respond( function ( request ) {
|
||||
assert.ok( request.url.match( /origin=/ ), 'origin is included in GET requests' );
|
||||
request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
|
||||
} );
|
||||
|
||||
api.get( {} );
|
||||
} );
|
||||
|
||||
QUnit.test( 'origin is included in POST requests', function ( assert ) {
|
||||
QUnit.expect( 2 );
|
||||
var api = new mw.ForeignApi( '//localhost:4242/w/api.php' );
|
||||
|
||||
this.server.respond( function ( request ) {
|
||||
assert.ok( request.requestBody.match( /origin=/ ), 'origin is included in POST request body' );
|
||||
assert.ok( request.url.match( /origin=/ ), 'origin is included in POST request URL, too' );
|
||||
request.respond( 200, { 'Content-Type': 'application/json' }, '[]' );
|
||||
} );
|
||||
|
||||
api.post( {} );
|
||||
} );
|
||||
|
||||
}( mediaWiki ) );
|
||||
Loading…
Reference in a new issue