Anomie has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/58924


Change subject: Add Javascript login check against the central wiki
......................................................................

Add Javascript login check against the central wiki

The ability for CentralAuth to log the user in everywhere via a "push"
model is going away, as browsers tighten their security on third-party
cookies.

This changes things to a "pull" model instead. If the user is logged
out, a request is sent to the central wiki, which puts the necessary
data in memcache so we can set the necessary cookies as first-party
instead. To avoid doing this with every page view, it is instead done
just once and a cookie or localStorage token is set to remember this.

Change-Id: Ib6e9cce4fa4c5f1482c59bfac28087f558786efe
---
M CentralAuth.alias.php
M CentralAuth.i18n.php
M CentralAuth.php
M CentralAuthHooks.php
A modules/ext.centralauth.centrallogin.js
A specials/SpecialCentralLogin.php
6 files changed, 637 insertions(+), 1 deletion(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/CentralAuth 
refs/changes/24/58924/1

diff --git a/CentralAuth.alias.php b/CentralAuth.alias.php
index cabd821..0042ff6 100644
--- a/CentralAuth.alias.php
+++ b/CentralAuth.alias.php
@@ -12,6 +12,7 @@
 $specialPageAliases['en'] = array(
        'CentralAuth' => array( 'CentralAuth' ),
        'AutoLogin' => array( 'AutoLogin' ),
+       'CentralLogin' => array( 'CentralLogin' ),
        'MergeAccount' => array( 'MergeAccount' ),
        'GlobalGroupMembership' => array( 'GlobalUserRights', 
'GlobalGroupMembership' ),
        'GlobalGroupPermissions' => array( 'GlobalGroupPermissions' ),
@@ -784,4 +785,4 @@
 /** Chinese (Hong Kong) (中文(香港)‎) */
 $specialPageAliases['zh-hk'] = array(
        'GlobalGroupMembership' => array( '全域用戶權限' ),
-);
\ No newline at end of file
+);
diff --git a/CentralAuth.i18n.php b/CentralAuth.i18n.php
index 41630a5..9088da8 100644
--- a/CentralAuth.i18n.php
+++ b/CentralAuth.i18n.php
@@ -303,6 +303,20 @@
 When you [[Special:UserLogin|log in]], the central login system instructs your 
browser to request this page from all linked domains, using image links.
 You have requested this page without providing any authentication data, so it 
does nothing.',
 
+       // Central login
+       'centrallogin'                    => 'Central login',
+       'centralauth-centrallogin-desc'   => 'This special page is used 
internally by MediaWiki.
+When you visit a linked domain while not logged in, the central login system 
uses this page to determine whether you are logged in to the central domain.
+You have requested this page without providing any authentication data, so it 
does nothing.',
+       'centralauth-centrallogin-badparams' => 'Authentication parameters 
specified were invalid',
+       'centralauth-centrallogin-lostsession' => 'Session data was lost',
+       'centralauth-centrallogin-badstate' => 'Invalid state "$1"',
+       'centralauth-centrallogin-notposted' => 'Central login form must be 
posted',
+       'centralauth-centrallogin-badstate-central' => 'State "$1" is not valid 
on the central wiki',
+       'centralauth-centrallogin-badstate-local' => 'State "$1" is not valid 
on the local wiki',
+       'centralauth-centrallogin-badwiki' => 'The wiki "$1" is not valid for 
central login',
+       'centralauth-centrallogin-corsfail' => 'CORS origin check failed',
+
        // Global group membership
        'globalgroupmembership' => 'Membership in global groups',
 
@@ -779,6 +793,21 @@
        'autologin' => '{{doc-special|AutoLogin|unlisted=1}}
 See example: [[w:Special:Autologin]].',
        'centralauth-autologin-desc' => 'This is the text shown on 
[[Special:AutoLogin]] when this page is requested by a user, not by the 
automatic global login system.',
+       'centrallogin' => '{{doc-special|CentralLogin|unlisted=1}}
+See example: [[w:Special:CentralLogin]].',
+       'centralauth-centrallogin-desc'   => 'This is the text shown on 
[[Special:CentralLogin]] when this page is requested by a user, not by the 
automatic global login system.',
+       'centralauth-centrallogin-badparams' => 'Error message when the 
required authentication parameters are missing or invalid',
+       'centralauth-centrallogin-lostsession' => 'Error message when the 
session data is lost or overwritten',
+       'centralauth-centrallogin-badstate' => 'Error message shown when an 
invalid state is given for [[Special:CentralLogin]].
+* $1 - Name of the state',
+       'centralauth-centrallogin-notposted' => 'Error message shown when 
[[Special:CentralLogin]] is called in form mode with an HTTP method other than 
POST',
+       'centralauth-centrallogin-badstate-central' => 'Error message shown 
when [[Special:CentralLogin]] is called on the central wiki with a state 
intended for use on the local wiki.
+* $1 - Name of the state',
+       'centralauth-centrallogin-badstate-local' => 'Error message shown when 
[[Special:CentralLogin]] is called on the local wiki with a state intended for 
use on the central wiki.
+* $1 - Name of the state',
+       'centralauth-centrallogin-badwiki' => 'Error message shown when an 
unacceptable wiki ID is given to [[Special:CentralLogin]].
+* $1 - The wiki ID',
+       'centralauth-centrallogin-corsfail' => 'Error message shown when the 
CORS origin check fails.',
        'globalgroupmembership' => '{{doc-special|GlobalGroupMembership}}',
        'globalgrouppermissions' => '{{doc-special|GlobalGroupPermissions}}
 See example: [[w:Special:GlobalGroupPermissions]] and 
[[w:Special:SpecialPages]]',
diff --git a/CentralAuth.php b/CentralAuth.php
index bfd06d9..eee1aeb 100644
--- a/CentralAuth.php
+++ b/CentralAuth.php
@@ -83,6 +83,11 @@
 $wgCentralAuthCookiePrefix = 'centralauth_';
 
 /**
+ * Wiki ID of the central domain.
+ */
+$wgCentralAuthCentralWiki = null;
+
+/**
  * List of wiki IDs which should be called on login/logout to set third-party
  * cookies for the global session state.
  *
@@ -161,6 +166,7 @@
 $wgAutoloadClasses['CentralAuthSuppressUserJob'] = 
"$caBase/SuppressUserJob.php";
 $wgAutoloadClasses['WikiSet'] = "$caBase/WikiSet.php";
 $wgAutoloadClasses['SpecialAutoLogin'] = 
"$caBase/specials/SpecialAutoLogin.php";
+$wgAutoloadClasses['SpecialCentralLogin'] = 
"$caBase/specials/SpecialCentralLogin.php";
 $wgAutoloadClasses['CentralAuthUserArray'] = 
"$caBase/CentralAuthUserArray.php";
 $wgAutoloadClasses['CentralAuthUserArrayFromResult'] = 
"$caBase/CentralAuthUserArray.php";
 $wgAutoloadClasses['SpecialGlobalGroupMembership'] = 
"$caBase/specials/SpecialGlobalGroupMembership.php";
@@ -201,6 +207,8 @@
 $wgHooks['MakeGlobalVariablesScript'][] = 
'CentralAuthHooks::onMakeGlobalVariablesScript';
 $wgHooks['SpecialPasswordResetOnSubmit'][] = 
'CentralAuthHooks::onSpecialPasswordResetOnSubmit';
 $wgHooks['OtherBlockLogLink'][] = 'CentralAuthHooks::getBlockLogLink';
+$wgHooks['BeforePageDisplay'][] = 'CentralAuthHooks::onBeforePageDisplay';
+$wgHooks['ResourceLoaderGetConfigVars'][] = 
'CentralAuthHooks::onResourceLoaderGetConfigVars';
 $wgHooks['ApiTokensGetTokenTypes'][] = 
'ApiDeleteGlobalAccount::injectTokenFunction';
 $wgHooks['ApiTokensGetTokenTypes'][] = 
'ApiSetGlobalAccountStatus::injectTokenFunction';
 
@@ -232,6 +240,7 @@
 
 $wgSpecialPages['CentralAuth'] = 'SpecialCentralAuth';
 $wgSpecialPages['AutoLogin'] = 'SpecialAutoLogin';
+$wgSpecialPages['CentralLogin'] = 'SpecialCentralLogin';
 $wgSpecialPages['MergeAccount'] = 'SpecialMergeAccount';
 $wgSpecialPages['GlobalGroupMembership'] = 'SpecialGlobalGroupMembership';
 $wgSpecialPages['GlobalGroupPermissions'] = 'SpecialGlobalGroupPermissions';
@@ -306,6 +315,10 @@
                'centralauth-admin-delete-confirm',
        ),
 ) + $commonModuleInfo;
+$wgResourceModules['ext.centralauth.centrallogin'] = array(
+       'scripts' => 'ext.centralauth.centrallogin.js',
+       'position' => 'top',
+) + $commonModuleInfo;
 
 $wgResourceModules['ext.centralauth.noflash'] = array(
        'styles' => 'ext.centralauth.noflash.css',
diff --git a/CentralAuthHooks.php b/CentralAuthHooks.php
index 3733186..f555a03 100644
--- a/CentralAuthHooks.php
+++ b/CentralAuthHooks.php
@@ -766,6 +766,34 @@
        }
 
        /**
+        * @param &$out OutputPage
+        * @param &$skin Skin
+        * @return bool
+        */
+       static function onBeforePageDisplay( &$out, &$skin ) {
+               global $wgCentralAuthCentralWiki;
+               if ( wfWikiID() !== $wgCentralAuthCentralWiki && 
$out->getUser()->isAnon() ) {
+                       $out->addModules( 'ext.centralauth.centrallogin' );
+               }
+               return true;
+       }
+
+       /**
+        * @param &$vars
+        * @return bool
+        */
+       static function onResourceLoaderGetConfigVars( &$vars ) {
+               global $wgUser, $wgCentralAuthCentralWiki;
+               if ( wfWikiID() !== $wgCentralAuthCentralWiki && 
$wgUser->isAnon() ) {
+                       $vars['wgCentralAuthWikiID'] = wfWikiID();
+                       $vars['wgCentralAuthCentralLoginEndpoint'] = 
WikiMap::getForeignURL(
+                               $wgCentralAuthCentralWiki, 
'Special:CentralLogin/$1'
+                       );
+               }
+               return true;
+       }
+
+       /**
         * @param $auth
         * @param $user User
         * @param $params
diff --git a/modules/ext.centralauth.centrallogin.js 
b/modules/ext.centralauth.centrallogin.js
new file mode 100644
index 0000000..557f5df
--- /dev/null
+++ b/modules/ext.centralauth.centrallogin.js
@@ -0,0 +1,131 @@
+( function ( mw, $, undefined ) {
+       // Are we already logged in?
+       if ( mw.config.get( 'wgUserName' ) !== null ) {
+               return;
+       }
+
+       // Do we already know we're logged out centrally?
+       if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'Userlogin' ) {
+               if ( 'localStorage' in window && +localStorage.getItem( 
'CentralAuthAnon' ) > new Date().getTime() ) {
+                       return;
+               }
+
+               // Can't use $.cookie(), because we want to check this at the 
top of
+               // the page and that isn't loaded until the bottom.
+               if ( /(^|; )CentralAuthAnon=1/.test( document.cookie ) ) {
+                       return;
+               }
+       }
+
+       var centralEndpoint = mw.config.get( 
'wgCentralAuthCentralLoginEndpoint' );
+       var localEndpoint = mw.config.get( 'wgArticlePath' ).replace( '$1', 
'Special:CentralLogin/$1' );
+       var wikiId = mw.config.get( 'wgCentralAuthWikiID' );
+
+       if ( jQuery.support.cors ) {
+               // We can do AJAX calls using CORS.
+               function C1() {
+                       $.ajax( {
+                               url: centralEndpoint.replace( '$1', 'C1' ),
+                               dataType: 'json',
+                               type: 'POST',
+                               data: { wikiid: wikiId },
+                               xhrFields: {
+                                       withCredentials: true
+                               },
+                               success: function ( ret ) {
+                                       if ( ret.status === 'ok' ) {
+                                               if ( +ret.gu_id <= 0 ) {
+                                                       var t = new Date();
+                                                       t.setTime( t.getTime() 
+ 86400000 );
+                                                       if ( 'localStorage' in 
window ) {
+                                                               
localStorage.setItem( 'CentralAuthAnon', t.getTime() );
+                                                       } else {
+                                                               document.cookie 
= 'CentralAuthAnon=1; expires=' + t.toGMTString() + '; path=/';
+                                                       }
+                                               } else {
+                                                       if ( 'localStorage' in 
window ) {
+                                                               
localStorage.removeItem( 'CentralAuthAnon' );
+                                                       }
+                                                       if ( /(^|; 
)CentralAuthAnon=/.test( document.cookie ) ) {
+                                                               document.cookie 
= 'CentralAuthAnon=0; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';
+                                                       }
+                                                       L1( ret.gu_id );
+                                               }
+                                       }
+                               }
+                       } );
+               }
+
+               function L1( gu_id ) {
+                       $.ajax( {
+                               url: localEndpoint.replace( '$1', 'L1' ),
+                               dataType: 'json',
+                               type: 'POST',
+                               data: { gu_id: gu_id },
+                               success: function ( ret ) {
+                                       if ( ret.status === 'ok' ) {
+                                               C2( ret.token );
+                                       }
+                               }
+                       } );
+               }
+
+               function C2( token ) {
+                       $.ajax( {
+                               url: centralEndpoint.replace( '$1', 'C2' ),
+                               dataType: 'json',
+                               type: 'POST',
+                               data: { token: token, wikiid: wikiId },
+                               xhrFields: {
+                                       withCredentials: true
+                               },
+                               success: function ( ret ) {
+                                       if ( ret.status === 'ok' ) {
+                                               L2();
+                                       }
+                               }
+                       } );
+               }
+
+               function L2() {
+                       $.ajax( {
+                               url: localEndpoint.replace( '$1', 'L2' ),
+                               dataType: 'json',
+                               type: 'POST',
+                               data: {},
+                               success: function ( ret ) {
+                                       if ( ret.status === 'ok' ) {
+                                               if ( mw.config.get( 
'wgCanonicalSpecialPageName' ) === 'Userlogin' && history.length > 1 ) {
+                                                       history.go( -1 );
+                                               } else {
+                                                       location.reload( true );
+                                               }
+                                       }
+                               }
+                       } );
+               }
+
+               C1();
+       } else {
+               // We have to do it with an iframe.
+               $( function () {
+                       var url = localEndpoint.replace( '$1', 'L0' );
+                       if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 
'Userlogin' && history.length > 1 ) {
+                               url += ( url.indexOf( '?' ) < 0 ? '?' : '&' ) + 
'back=1';
+                       }
+                       $( '<iframe>' )
+                               .css( {
+                                       position: 'absolute',
+                                       top: 0,
+                                       left: '-10px',
+                                       width: '1px',
+                                       height: '1px'
+                               } )
+                               .attr( {
+                                       src: url
+                               } )
+                               .appendTo( document.body );
+               } );
+       }
+
+}( mediaWiki, jQuery ) );
diff --git a/specials/SpecialCentralLogin.php b/specials/SpecialCentralLogin.php
new file mode 100644
index 0000000..db3d4db
--- /dev/null
+++ b/specials/SpecialCentralLogin.php
@@ -0,0 +1,434 @@
+<?php
+
+/**
+ * Unlisted Special page to set requisite cookies for being logged into this 
wiki.
+ *
+ * @ingroup Extensions
+ */
+class SpecialCentralLogin extends UnlistedSpecialPage {
+       private $isForm = false;
+
+       function __construct() {
+               parent::__construct( 'CentralLogin' );
+       }
+
+       function execute( $par ) {
+               global $wgMemc;
+
+               switch ( $par ) {
+               case null:
+                       $this->setHeaders();
+                       $this->getOutput()->addWikiMsg( 
'centralauth-centrallogin-desc' );
+                       return;
+
+               case 'L0': // Output form for C1
+                       $this->isForm = 1;
+                       $data = array(
+                               'status' => 'ok',
+                               'nextState' => 'C1',
+                               'params' => array(
+                                       'wikiid' => wfWikiID(),
+                               ),
+                       );
+                       break;
+
+               case 'C1': // Query gu_id
+                       $data = $this->checkInputState( $par, true );
+                       if ( $data ) {
+                               break;
+                       }
+
+                       global $wgUser;
+                       $centralUser = CentralAuthUser::getInstance( $wgUser );
+                       $data = array(
+                               'status' => 'ok',
+                               'nextState' => 'L1',
+                               'params' => array(
+                                       'gu_id' => $centralUser ? 
$centralUser->getId() : 0,
+                               )
+                       );
+                       break;
+
+               case 'L1': // Start session for gu_id
+                       $data = $this->checkInputState( $par, false );
+                       if ( $data ) {
+                               break;
+                       }
+
+                       $gu_id = +$this->getRequest()->getVal( 'gu_id', 0 );
+                       if ( $gu_id <= 0 ) {
+                               // Should only get here for iframe mode
+                               $script = "var t = new Date();\n" .
+                                       "t.setTime( t.getTime() + 86400000 
);\n" .
+                                       "if ( 'localStorage' in window ) {\n" .
+                                       "\tlocalStorage.setItem( 
'CentralAuthAnon', t.getTime() );\n" .
+                                       "} else {\n" .
+                                       "\tdocument.cookie = 
'CentralAuthAnon=1; expires=' + t.toGMTString() + '; path=/';\n" .
+                                       "}\n";
+                               $data = array(
+                                       'status' => 'script',
+                                       'script' => $script,
+                               );
+                               break;
+                       }
+
+                       $script = "if ( 'localStorage' in window ) {\n" .
+                               "\tlocalStorage.removeItem( 'CentralAuthAnon' 
);\n" .
+                               "}\n" .
+                               "if ( /(^|; )CentralAuthAnon=/.test( 
document.cookie ) ) {\n" .
+                               "\tdocument.cookie = 'CentralAuthAnon=0; 
expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';\n" .
+                               "}\n";
+
+                       // Ensure that a session exists
+                       if ( session_id() == '' ) {
+                               wfSetupSession();
+                       }
+
+                       // Create memc token
+                       $wikiid = wfWikiID();
+                       $memcData = array(
+                               'gu_id' => $gu_id,
+                               'wikiid' => $wikiid,
+                       );
+                       do {
+                               $token = MWCryptRand::generateHex( 32 );
+                               $key = CentralAuthUser::memcKey( 
'centrallogin-token', $token, $wikiid );
+                       } while ( !$wgMemc->add( $key, $memcData, 10 ) );
+
+                       // Save memc token for L2
+                       $this->getRequest()->setSessionData( 
'centrallogin-token', $token );
+
+                       $data = array(
+                               'status' => 'ok',
+                               'nextState' => 'C2',
+                               'script' => $script,
+                               'params' => array(
+                                       'token' => $token,
+                                       'wikiid' => wfWikiID(),
+                               )
+                       );
+                       break;
+
+               case 'C2': // Complete session for memc token
+                       $data = $this->checkInputState( $par, true );
+                       if ( $data ) {
+                               break;
+                       }
+
+                       // Validate params
+                       $wikiid = $this->getRequest()->getVal( 'wikiid', '' );
+                       $token = $this->getRequest()->getVal( 'token', '' );
+                       if ( $token === '' || $wikiid === '' ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badparams' )
+                               );
+                               break;
+                       }
+
+                       // Load memc data
+                       $key = CentralAuthUser::memcKey( 'centrallogin-token', 
$token, $wikiid );
+                       $memcData = $wgMemc->get( $key );
+                       $wgMemc->delete( $key );
+
+                       // Check memc data
+                       global $wgUser;
+                       $centralUser = CentralAuthUser::getInstance( $wgUser );
+                       if ( !$memcData ||
+                               $memcData['wikiid'] !== $wikiid ||
+                               !$centralUser ||
+                               !$centralUser->getId() ||
+                               $memcData['gu_id'] != $centralUser->getId()
+                       ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badparams' )
+                               );
+                               break;
+                       }
+
+                       // Write info for session creation into memc
+                       $memcData += array(
+                               'userName' => $centralUser->getName(),
+                               'token' => $centralUser->getAuthToken(),
+                       );
+                       $wgMemc->set( $key, $memcData, 10 );
+
+                       $data = array(
+                               'status' => 'ok',
+                               'nextState' => 'L2',
+                               'params' => array(),
+                       );
+                       break;
+
+               case 'L2': // Set cookies for session in memc
+                       $data = $this->checkInputState( $par, false );
+                       if ( $data ) {
+                               break;
+                       }
+
+                       // Check saved memc token
+                       $token = $this->getRequest()->getSessionData( 
'centrallogin-token' );
+                       if ( $token === null ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-lostsession' ),
+                               );
+                               break;
+                       }
+
+                       // Load memc data
+                       $wikiid = wfWikiID();
+                       $key = CentralAuthUser::memcKey( 'centrallogin-token', 
$token, $wikiid );
+                       $memcData = $wgMemc->get( $key );
+                       $wgMemc->delete( $key );
+
+                       // Check memc data
+                       if ( !$memcData ||
+                               $memcData['wikiid'] !== $wikiid ||
+                               !isset( $memcData['userName'] ) ||
+                               !isset( $memcData['token'] )
+                       ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-lostsession' ),
+                               );
+                               break;
+                       }
+
+                       // Load and check CentralAuthUser
+                       $centralUser = new CentralAuthUser( 
$memcData['userName'] );
+                       if ( !$centralUser->getId() || $centralUser->getId() != 
$memcData['gu_id'] ) {
+                               $msg = "Wrong user: expected 
{$memcData['gu_id']}, got {$centralUser->getId()}";
+                               wfDebug( __METHOD__ . ": $msg\n" );
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-lostsession' ),
+                               );
+                               break;
+                       }
+                       $loginResult = $centralUser->authenticateWithToken( 
$memcData['token'] );
+                       if ( $loginResult != 'ok' ) {
+                               $msg = "Bad token: $loginResult";
+                               wfDebug( __METHOD__ . ": $msg\n" );
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-lostsession' ),
+                               );
+                               break;
+                       }
+
+                       // Ok. Set cookies.
+                       $centralUser->setGlobalCookies( false );
+
+                       if ( $this->getRequest()->getBool( 'back' ) ) {
+                               $script = 'top.history.go( -1 );';
+                       } else {
+                               $script = 'top.location.reload( true );';
+                       }
+
+                       $data = array(
+                               'status' => 'ok',
+                               'script' => $script,
+                               'params' => array(),
+                       );
+                       break;
+
+               default:
+                       $data = array(
+                               'status' => 'error',
+                               'msg' => array( 
'centralauth-centrallogin-badstate', $par ),
+                       );
+                       break;
+               }
+
+               $this->outputData( $data );
+       }
+
+       private function checkInputState( $par, $central ) {
+               global $wgCentralAuthCentralWiki;
+
+               $request = $this->getRequest();
+               $this->isForm = $request->getBool( 'form' );
+
+               // Make sure it was posted.
+               if ( !$request->wasPosted() && $request->getMethod() !== 
'OPTIONS' ) {
+                       return array(
+                               'status' => 'error',
+                               'msg' => array( 
'centralauth-centrallogin-notposted' ),
+                       );
+               }
+
+               // Validate the state for this wiki
+               if ( $central ) {
+                       if ( wfWikiID() !== $wgCentralAuthCentralWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badstate-central', $par ),
+                               );
+                       }
+
+                       $wikiId = $request->getVal( 'wikiid' );
+                       if ( $wikiId === $wgCentralAuthCentralWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badwiki', $wikiId ),
+                               );
+                       }
+                       $wiki = WikiMap::getWiki( $wikiId );
+                       if ( !$wiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badwiki', $wikiId ),
+                               );
+                       }
+
+                       // CORS request, validate origin and set CORS headers
+                       if ( !$this->isForm ) {
+                               $response = $request->response();
+
+                               $originHeader = $request->getHeader( 'Origin' );
+                               if ( $originHeader === false ) {
+                                       $origins = array();
+                               } else {
+                                       $origins = explode( ' ', $originHeader 
);
+                               }
+
+                               $wikiOrigin = 'http://' . strtolower( 
$wiki->getHostname() );
+                               $ok = false;
+                               foreach ( $origins as $origin ) {
+                                       if ( $wikiOrigin === str_replace( 
'https://', 'http://', strtolower( $origin ) ) ) {
+                                               $wikiOrigin = $origin;
+                                               $ok = true;
+                                               break;
+                                       }
+                               }
+                               if ( !$ok ) {
+                                       $message = HttpStatus::getMessage( 403 
);
+                                       $response->header( "HTTP/1.1 403 
$message", true, 403 );
+                                       return array(
+                                               'status' => 'error',
+                                               'msg' => array( 
'centralauth-centrallogin-corsfail' )
+                                       );
+                               }
+
+                               $response->header( 
"Access-Control-Allow-Origin: $wikiOrigin" );
+                               $response->header( 
'Access-Control-Allow-Credentials: true' );
+                               $this->getOutput()->addVaryHeader( 'Origin' );
+                       }
+               } else {
+                       if ( wfWikiID() === $wgCentralAuthCentralWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centrallogin-badstate-local', $par ),
+                               );
+                       }
+               }
+
+               if ( $request->getMethod() === 'OPTIONS' ) {
+                       return array(
+                               'status' => 'cors'
+                       );
+               }
+
+               return null;
+       }
+
+       private function outputData( $data ) {
+               global $wgMimeType, $wgLanguageCode;
+
+               $output = $this->getOutput();
+               $output->enableClientCache( false );
+               $output->sendCacheControl();
+               $output->disable();
+               $response = $this->getRequest()->response();
+
+               if ( !$this->isForm ) {
+                       $frameOptions = $output->getFrameOptions();
+                       if ( $frameOptions ) {
+                               $response->header( "X-Frame-Options: 
$frameOptions" );
+                       }
+               }
+
+               if ( $this->isForm ) {
+                       $script='';
+                       $bodyParams = array();
+                       $body='';
+                       switch ( $data['status'] ) {
+                       case 'script':
+                               $script = $data['script'];
+                               break;
+
+                       case 'ok':
+                               if ( isset( $data['script'] ) ) {
+                                       $script = $data['script'];
+                               }
+
+                               if ( isset( $data['nextState'] ) ) {
+                                       $script .= "\n\nfunction doSubmit() 
{\n" .
+                                               "\tif ( document.forms[0] ) 
{\n" .
+                                               
"\t\tdocument.forms[0].submit();\n" .
+                                               "\t}\n" .
+                                               "}";
+                                       $bodyParams['onload'] = 'doSubmit()';
+
+                                       if ( substr( $data['nextState'], 0, 1 ) 
=== 'C' ) {
+                                               global 
$wgCentralAuthCentralWiki;
+                                               $target = 
$wgCentralAuthCentralWiki;
+                                       } else {
+                                               $target = 
$this->getRequest()->getVal( 'wikiid', wfWikiID() );
+                                       }
+                                       $body .= "\n" . Html::openElement( 
'form', array(
+                                               'method' => 'POST',
+                                               'action' => 
WikiMap::getForeignURL( $target, 'Special:CentralLogin/' . $data['nextState'] ),
+                                       ) ) . "\n";
+                                       $body .= Html::hidden( 'form', '1' ) . 
"\n";
+                                       if ( $this->getRequest()->getBool( 
'back' ) ) {
+                                               $body .= Html::hidden( 'back', 
'1' ) . "\n";
+                                       }
+                                       foreach ( $data['params'] as $k => $v ) 
{
+                                               $body .= Html::hidden( $k, $v ) 
. "\n";
+                                       }
+                                       $body .= Html::closeElement( 'form' );
+                               }
+                               break;
+
+                       case 'error':
+                               $params = $data['msg'];
+                               $key = array_shift( $params );
+                               $body =  wfMessage( $key, $params )->escaped();
+                               break;
+                       }
+
+                       $response->header( "Content-type: $wgMimeType; 
charset=UTF-8" );
+                       $response->header( 'Content-language: ' . 
$wgLanguageCode );
+                       print Html::htmlHeader();
+                       print Html::openElement( 'head' );
+                       print Html::element( 'title', null, 
$output->getHTMLTitle() );
+                       if ( $script !== '' ) {
+                               print Html::inlineScript( $script );
+                       }
+                       print Html::closeElement( 'head' );
+                       print Html::openElement( 'body', $bodyParams );
+                       print $body;
+                       print Html::closeElement( 'body' );
+                       print Html::closeElement( 'html' );
+               } else {
+                       $response->header( "Content-type: application/json; 
charset=UTF-8" );
+                       $response->header( 'Content-language: ' . 
$wgLanguageCode );
+                       switch ( $data['status'] ) {
+                       case 'cors':
+                               break;
+
+                       case 'ok':
+                               $data['params']['status'] = 'ok';
+                               print FormatJson::encode( $data['params'] );
+                               break;
+
+                       default:
+                               print FormatJson::encode( $data );
+                               break;
+                       }
+               }
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/58924
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ib6e9cce4fa4c5f1482c59bfac28087f558786efe
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/CentralAuth
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to