jenkins-bot has submitted this change and it was merged.

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.

Note that, to allow Internet Explorer to be able to send and receive the
cookies via CORS or the iframe, a P3P header must be included when the
cookies are set on the central domain. The default policy used is
clearly invalid: it has no valid tokens at all, while a real policy must
contain at least an "access" token and one statement, which would
contain either the NID token or at least one "purpose" token, one
"recipient" token, and one "retention" token.

Change-Id: Ib6e9cce4fa4c5f1482c59bfac28087f558786efe
---
M CentralAuth.alias.php
M CentralAuth.i18n.php
M CentralAuth.php
M CentralAuthHooks.php
M CentralAuthUser.php
A modules/ext.centralauth.centralautologin.js
A specials/SpecialCentralAutoLogin.php
M specials/SpecialCentralLogin.php
8 files changed, 794 insertions(+), 6 deletions(-)

Approvals:
  Tim Starling: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/CentralAuth.alias.php b/CentralAuth.alias.php
index 6944c7f..71b4140 100644
--- a/CentralAuth.alias.php
+++ b/CentralAuth.alias.php
@@ -12,6 +12,7 @@
 $specialPageAliases['en'] = array(
        'CentralAuth' => array( 'CentralAuth' ),
        'AutoLogin' => array( 'AutoLogin' ),
+       'CentralAutoLogin' => array( 'CentralAutoLogin' ),
        'MergeAccount' => array( 'MergeAccount' ),
        'GlobalGroupMembership' => array( 'GlobalUserRights', 
'GlobalGroupMembership' ),
        'GlobalGroupPermissions' => array( 'GlobalGroupPermissions' ),
@@ -786,4 +787,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 5e5a76c..7d661f9 100644
--- a/CentralAuth.i18n.php
+++ b/CentralAuth.i18n.php
@@ -303,6 +303,25 @@
 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
+       'centralautologin'                    => 'Central login',
+       'centralauth-centralautologin-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-centralautologin-alreadyloggedinlocally' => 'You are 
already logged in locally',
+       'centralauth-centralautologin-badparams' => 'Authentication parameters 
specified were invalid',
+       'centralauth-centralautologin-lostsession' => 'Session data was lost',
+       'centralauth-centralautologin-badstate' => 'Invalid state "$1"',
+       'centralauth-centralautologin-notposted' => 'Central login form must be 
posted',
+       'centralauth-centralautologin-badstate-central' => 'State "$1" is not 
valid on the central wiki',
+       'centralauth-centralautologin-badstate-local' => 'State "$1" is not 
valid on the local wiki',
+       'centralauth-centralautologin-badwiki' => 'The wiki "$1" is not valid 
for central login',
+       'centralauth-centralautologin-corsfail' => 'CORS origin check failed',
+       'centralauth-centralautologin-p3p-explanation' => 'Certain browsers 
require a P3P compact policy for cookies to be sent or received in some cases, 
including the situation involved in the SUL login check. This is a needless 
hoop to jump through in this situation, considering that all pages involved are 
in the same wiki group and P3P itself is mostly obsolete and abandoned; 
fortunately, the invalid policy linking to this page is currently considered 
"good enough" by these browsers\' default configurations.
+
+Please review this site\'s human-readable privacy policy to determine how any 
information collected might be used.',
+       'centralauth-centralautologin-logged-in' => 'You are centrally logged 
in as $1. Reload the page to apply your user settings.',
+
        // Global group membership
        'globalgroupmembership' => 'Membership in global groups',
 
@@ -818,6 +837,26 @@
        '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.',
+       'centralautologin' => '{{doc-special|CentralAutoLogin|unlisted=1}}
+See example: [[w:Special:CentralAutoLogin]].',
+       'centralauth-centralautologin-desc'   => 'This is the text shown on 
[[Special:CentralAutoLogin]] when this page is requested by a user, not by the 
automatic global login system.',
+       'centralauth-centralautologin-alreadyloggedinlocally' => 'Error message 
shown when [[Special:CentralAutoLogin]] is called on the local wiki while the 
user is already logged in.',
+       'centralauth-centralautologin-badparams' => 'Error message when the 
required authentication parameters are missing or invalid',
+       'centralauth-centralautologin-lostsession' => 'Error message when the 
session data is lost or overwritten',
+       'centralauth-centralautologin-badstate' => 'Error message shown when an 
invalid state is given for [[Special:CentralAutoLogin]].
+* $1 - Name of the state',
+       'centralauth-centralautologin-notposted' => 'Error message shown when 
[[Special:CentralAutoLogin]] is called in form mode with an HTTP method other 
than POST',
+       'centralauth-centralautologin-badstate-central' => 'Error message shown 
when [[Special:CentralAutoLogin]] is called on the central wiki with a state 
intended for use on the local wiki.
+* $1 - Name of the state',
+       'centralauth-centralautologin-badstate-local' => 'Error message shown 
when [[Special:CentralAutoLogin]] is called on the local wiki with a state 
intended for use on the central wiki.
+* $1 - Name of the state',
+       'centralauth-centralautologin-badwiki' => 'Error message shown when an 
unacceptable wiki ID is given to [[Special:CentralAutoLogin]].
+* $1 - The wiki ID',
+       'centralauth-centralautologin-corsfail' => 'Error message shown when 
the CORS origin check fails.',
+       'centralauth-centralautologin-p3p-explanation' => 'Explanation for the 
P3P hack that allows IE to set cookies.',
+       'centralauth-centralautologin-logged-in' => 'Message displayed by 
JavaScript when the auto-login succeeds.
+* $1 - User name
+* $2 - User gender code {{gender}}',
        '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 d30a947..3c108a8 100644
--- a/CentralAuth.php
+++ b/CentralAuth.php
@@ -73,7 +73,7 @@
  * login wiki will use a session/cookie to handle unified login sessions 
across wikis.
  *
  * On login, users will be redirected to the login wiki's 
Special:CentralLogin/login
- * page and then redirected to Special:CentralAutoLogin back on the 
originating wiki.
+ * page and then redirected to Special:CentralLogin back on the originating 
wiki.
  * In the process, the central login wiki cookie and session will be set.
  * As the user accesses other wikis, the login wiki will be checked via 
JavaScript
  * to check login status and set the local session and cookies.
@@ -122,6 +122,25 @@
  * Should be a 20x20px PNG.
  */
 $wgCentralAuthLoginIcon = false;
+
+/**
+ * Specify a P3P header value to be used when setting CentralAuth cookies on
+ * the login wiki ($wgCentralAuthLoginWiki).
+ *
+ * When set true, a invalid policy (lacking all required tokens) will be sent
+ * that none the less serves to allow current versions of IE with the default
+ * privacy settings to see the cookies in the auto-login check.
+ *
+ * Set false to disable sending the P3P header altogether. Note this will
+ * likely break the auto-login check in IE, unless the header is being set
+ * globally elsewhere (e.g. in the webserver).
+ *
+ * Otherwise, whatever string is assigned here will be sent as the value of the
+ * P3P header.
+ *
+ * @var bool|string
+ */
+$wgCentralAuthCookiesP3P = true;
 
 /**
  * If true, local accounts will be created for active global sessions
@@ -177,6 +196,7 @@
 $wgAutoloadClasses['CentralAuthSuppressUserJob'] = 
"$caBase/SuppressUserJob.php";
 $wgAutoloadClasses['WikiSet'] = "$caBase/WikiSet.php";
 $wgAutoloadClasses['SpecialAutoLogin'] = 
"$caBase/specials/SpecialAutoLogin.php";
+$wgAutoloadClasses['SpecialCentralAutoLogin'] = 
"$caBase/specials/SpecialCentralAutoLogin.php";
 $wgAutoloadClasses['CentralAuthUserArray'] = 
"$caBase/CentralAuthUserArray.php";
 $wgAutoloadClasses['CentralAuthUserArrayFromResult'] = 
"$caBase/CentralAuthUserArray.php";
 $wgAutoloadClasses['SpecialGlobalGroupMembership'] = 
"$caBase/specials/SpecialGlobalGroupMembership.php";
@@ -218,6 +238,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';
 
@@ -250,6 +272,7 @@
 $wgSpecialPages['CentralAuth'] = 'SpecialCentralAuth';
 $wgSpecialPages['CentralLogin'] = 'SpecialCentralLogin';
 $wgSpecialPages['AutoLogin'] = 'SpecialAutoLogin';
+$wgSpecialPages['CentralAutoLogin'] = 'SpecialCentralAutoLogin';
 $wgSpecialPages['MergeAccount'] = 'SpecialMergeAccount';
 $wgSpecialPages['GlobalGroupMembership'] = 'SpecialGlobalGroupMembership';
 $wgSpecialPages['GlobalGroupPermissions'] = 'SpecialGlobalGroupPermissions';
@@ -327,6 +350,18 @@
                'centralauth-completelogin-finishing'
        ),
 ) + $commonModuleInfo;
+$wgResourceModules['ext.centralauth.centralautologin'] = array(
+       'scripts' => 'ext.centralauth.centralautologin.js',
+       'position' => 'top',
+       'dependencies' => array(
+               'mediawiki.notify',
+               'mediawiki.jqueryMsg',
+       ),
+       'messages' => array(
+               'centralautologin',
+               'centralauth-centralautologin-logged-in',
+       ),
+) + $commonModuleInfo;
 
 $wgResourceModules['ext.centralauth.noflash'] = array(
        'styles' => 'ext.centralauth.noflash.css',
diff --git a/CentralAuthHooks.php b/CentralAuthHooks.php
index 78e0e4b..36cbd03 100644
--- a/CentralAuthHooks.php
+++ b/CentralAuthHooks.php
@@ -881,6 +881,32 @@
        }
 
        /**
+        * @param &$out OutputPage
+        * @param &$skin Skin
+        * @return bool
+        */
+       static function onBeforePageDisplay( &$out, &$skin ) {
+               global $wgCentralAuthLoginWiki;
+               if ( $wgCentralAuthLoginWiki && wfWikiID() !== 
$wgCentralAuthLoginWiki && $out->getUser()->isAnon() ) {
+                       $out->addModules( 'ext.centralauth.centralautologin' );
+               }
+               return true;
+       }
+
+       /**
+        * @param &$vars
+        * @return bool
+        */
+       static function onResourceLoaderGetConfigVars( &$vars ) {
+               global $wgUser, $wgCentralAuthLoginWiki;
+               $vars['wgCentralAuthWikiID'] = wfWikiID();
+               $vars['wgCentralAuthCentralAutoLoginEndpoint'] = wfExpandUrl( 
WikiMap::getForeignURL(
+                       $wgCentralAuthLoginWiki, 'Special:CentralAutoLogin/$1'
+               ), PROTO_HTTPS );
+               return true;
+       }
+
+       /**
         * @param $auth
         * @param $user User
         * @param $params
diff --git a/CentralAuthUser.php b/CentralAuthUser.php
index 2ec4d0f..5592b0c 100644
--- a/CentralAuthUser.php
+++ b/CentralAuthUser.php
@@ -2002,6 +2002,23 @@
                        $remember = $remember->getOption( 'rememberpassword' );
                }
 
+               // IE requires that a P3P header be provided for the cookies to 
be
+               // visible to the js auto-login check.
+               global $wgCentralAuthCookiesP3P, $wgCentralAuthLoginWiki;
+               if ( $wgCentralAuthLoginWiki === wfWikiID() ) {
+                       if ( $wgCentralAuthCookiesP3P === true ) {
+                               // Note this policy is not valid: it has no 
valid tokens, while
+                               // a valid policy would contain an "access" 
token and at least
+                               // one statement, which would contain either 
the NID token or
+                               // at least one "purpose" token, one 
"recipient" token, and one
+                               // "retention" token.
+                               $url = Title::makeTitle( NS_SPECIAL, 
'CentralAutoLogin/P3P' )->getCanonicalURL();
+                               header( "P3P: CP=\"This is not a P3P policy! 
See $url for more info.\"", true );
+                       } elseif ( $wgCentralAuthCookiesP3P ) {
+                               header( "P3P: $wgCentralAuthCookiesP3P", true );
+                       }
+               }
+
                $session = array();
                $session['user'] = $this->mName;
                self::setCookie( 'User', $this->mName );
diff --git a/modules/ext.centralauth.centralautologin.js 
b/modules/ext.centralauth.centralautologin.js
new file mode 100644
index 0000000..b154b7a
--- /dev/null
+++ b/modules/ext.centralauth.centralautologin.js
@@ -0,0 +1,169 @@
+( function ( mw, $ ) {
+       // 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( 
'wgCentralAuthCentralAutoLoginEndpoint' ),
+               localEndpoint = mw.config.get( 'wgArticlePath' ).replace( '$1', 
'Special:CentralAutoLogin/$1' ),
+               wikiId = mw.config.get( 'wgCentralAuthWikiID' );
+
+       function getReturnToParams() {
+               var params = location.search.slice( 1 ).split( '&' ),
+                       len = params.length,
+                       param, i,
+                       ret = [];
+               for ( i = 0; i < len; i++ ) {
+                       param = params[i].split( '=' );
+                       param = decodeURIComponent( param[0] );
+                       if ( param === 'returnto' || param === 'returntoquery' 
) {
+                               ret.push( params[i] );
+                       }
+               }
+               return ret.join( '&' );
+       }
+
+       // These functions should be declared in the "if ( jQuery.support.cors 
)"
+       // where they're actually needed, but jshint insists they be placed up 
here
+       // instead.
+       // Documentation: [[mw:Auth systems/SUL2#Local wiki Javascript Auth 
check]]
+
+       // State C1
+       function fetchCentralUserId() {
+               // Also, disable this brain-damaged warning. We're not going to 
rewrite
+               // all the PHP code because it doesn't like "ret.gu_id".
+               /*jshint camelcase: false */
+               $.ajax( {
+                       url: centralEndpoint.replace( '$1', 'C1' ),
+                       dataType: 'json',
+                       type: 'POST',
+                       data: { wikiid: wikiId },
+                       xhrFields: {
+                               withCredentials: true
+                       }
+               } ).done( function ( ret ) {
+                       /*jshint camelcase: false */
+                       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=/';
+                                       }
+                                       createLocalSessionForCentralLogin( 
ret.gu_id );
+                               }
+                       }
+               } );
+       }
+
+       // State L1
+       function createLocalSessionForCentralLogin( guid ) {
+               /*jshint camelcase: false */
+               $.ajax( {
+                       url: localEndpoint.replace( '$1', 'L1' ),
+                       dataType: 'json',
+                       type: 'POST',
+                       data: { gu_id: guid }
+               } ).done( function ( ret ) {
+                       if ( ret.status === 'ok' ) {
+                               haveCentralWikiPutLoginDataInMemcached( 
ret.token );
+                       }
+               } );
+       }
+
+       // State C2
+       function haveCentralWikiPutLoginDataInMemcached( token ) {
+               $.ajax( {
+                       url: centralEndpoint.replace( '$1', 'C2' ),
+                       dataType: 'json',
+                       type: 'POST',
+                       data: { token: token, wikiid: wikiId },
+                       xhrFields: {
+                               withCredentials: true
+                       }
+               } ).done( function ( ret ) {
+                       if ( ret.status === 'ok' ) {
+                               checkMemcachedDataAndSetCentralAuthCookies();
+                       }
+               } );
+       }
+
+       // State L2
+       function checkMemcachedDataAndSetCentralAuthCookies() {
+               $.ajax( {
+                       url: localEndpoint.replace( '$1', 'L2' ),
+                       dataType: 'json',
+                       type: 'POST',
+                       data: {}
+               } ).done( function ( ret ) {
+                       if ( ret.status === 'ok' ) {
+                               if ( mw.config.get( 
'wgCanonicalSpecialPageName' ) === 'Userlogin' ) {
+                                       var url = mw.config.get( 
'wgArticlePath' ).replace( '$1', 'Special:CentralLogin/Status' );
+                                       url += ( url.indexOf( '?' ) < 0 ? '?' : 
'&' ) + getReturnToParams();
+                                       location.href = url;
+                               } else {
+                                       mw.notify(
+                                               mw.message( 
'centralauth-centralautologin-logged-in',
+                                                       ret.userName, 
ret.userGender
+                                               ), {
+                                                       title: mw.message( 
'centralautologin' ).plain(),
+                                                       autoHide: false,
+                                                       tag: 'CentralAutoLogin'
+                                               }
+                                       );
+                               }
+                       }
+               } );
+       }
+
+       if ( jQuery.support.cors ) {
+               // We can do AJAX calls using CORS.
+
+               // Start by calling state C1
+               fetchCentralUserId();
+       } else {
+               // We have to do it with an iframe. State "L0" returns the first
+               // auto-posting form for us, and the protocol proceeds from 
there
+               // without our intervention.
+               $( function () {
+                       var url = localEndpoint.replace( '$1', 'L0' );
+                       if ( mw.config.get( 'wgCanonicalSpecialPageName' ) === 
'Userlogin' ) {
+                               url += ( url.indexOf( '?' ) < 0 ? '?' : '&' ) + 
'oncomplete=status';
+                               url += '&' + getReturnToParams();
+                       }
+                       $( '<iframe>' )
+                               .css( {
+                                       position: 'absolute',
+                                       top: 0,
+                                       left: '-10px',
+                                       width: '1px',
+                                       height: '1px'
+                               } )
+                               .attr( 'src', url )
+                               .appendTo( document.body );
+               } );
+       }
+
+}( mediaWiki, jQuery ) );
diff --git a/specials/SpecialCentralAutoLogin.php 
b/specials/SpecialCentralAutoLogin.php
new file mode 100644
index 0000000..6d89a13
--- /dev/null
+++ b/specials/SpecialCentralAutoLogin.php
@@ -0,0 +1,501 @@
+<?php
+
+/**
+ * Unlisted Special page to set requisite cookies for being logged into this 
wiki.
+ *
+ * @ingroup Extensions
+ */
+class SpecialCentralAutoLogin extends UnlistedSpecialPage {
+       private $isForm = false;
+
+       function __construct() {
+               parent::__construct( 'CentralAutoLogin' );
+       }
+
+       function execute( $par ) {
+               global $wgMemc, $wgUser;
+
+               switch ( strval( $par ) ) {
+               case '': // also null and false
+                       $this->setHeaders();
+                       $this->getOutput()->addWikiMsg( 
'centralauth-centralautologin-desc' );
+                       return;
+
+               case 'P3P': // Explain the bogus P3P header
+                       $this->setHeaders();
+                       $this->getOutput()->addWikiMsg( 
'centralauth-centralautologin-p3p-explanation' );
+                       return;
+
+               case 'L0': // Output form for C1
+                       $this->isForm = 1;
+                       if ( !$wgUser->isAnon() ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-alreadyloggedinlocally' ),
+                               );
+                               break;
+                       }
+                       $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( 
'centralautologin-token', $token, $wikiid );
+                       } while ( !$wgMemc->add( $key, $memcData, 10 ) );
+
+                       // Save memc token for L2
+                       $this->getRequest()->setSessionData( 
'centralautologin-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-centralautologin-badparams' )
+                               );
+                               break;
+                       }
+
+                       // Load memc data
+                       $key = CentralAuthUser::memcKey( 
'centralautologin-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-centralautologin-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( 
'centralautologin-token' );
+                       if ( $token === null ) {
+                               $data = array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-lostsession' ),
+                               );
+                               break;
+                       }
+
+                       // Load memc data
+                       $wikiid = wfWikiID();
+                       $key = CentralAuthUser::memcKey( 
'centralautologin-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-centralautologin-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-centralautologin-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-centralautologin-lostsession' ),
+                               );
+                               break;
+                       }
+
+                       // Set a new session cookie, Just In Case™
+                       // If either we don't trust PHP's entropy, or if we 
need to change
+                       // cookie settings when logging in, then change the 
session ID
+                       // manually.
+                       global $wgCookieSecure;
+                       $cookieParams = session_get_cookie_params();
+                       if ( wfCheckEntropy() && $wgCookieSecure == 
$cookieParams['secure'] ) {
+                               session_regenerate_id( false );
+                       } else {
+                               $tmp = $_SESSION;
+                               session_destroy();
+                               wfSetupSession( MWCryptRand::generateHex( 32 ) 
);
+                               $_SESSION = $tmp;
+                       }
+
+                       // Set central cookies too
+                       $centralUser->setGlobalCookies( false );
+
+                       $data = array(
+                               'status' => 'ok',
+                               'params' => array(),
+                       );
+
+                       switch ( $this->getRequest()->getVal( 'oncomplete', 
'mw.notify' ) ) {
+                       case 'mw.notify':
+                               $user = User::newFromName( 
$centralUser->getName() );
+                               $gender = $user->getOption( 'gender' );
+                               if ( strval( $gender ) === '' ) {
+                                       $gender = 'unknown';
+                               }
+                               $data['script'] = Xml::encodeJsCall( 
'top.mw.notify', array(
+                                       new XmlJsCode( trim( Xml::encodeJsCall( 
'top.mw.message', array(
+                                               
'centralauth-centralautologin-logged-in',
+                                               $user->getName(),
+                                               $gender
+                                       ) ), ';' ) ),
+                                       array(
+                                               'title' => wfMessage( 
'centralautologin' )->plain(),
+                                               'autoHide' => false,
+                                               'tag' => 'CentralAutoLogin',
+                                       ),
+                               ) );
+                               $data['params']['userName'] = $user->getName();
+                               $data['params']['userGender'] = $gender;
+                               break;
+
+                       case 'status':
+                               $request = $this->getRequest();
+                               $url = Title::newFromText( 
'Special:CentralLogin/Status' )->getFullUrl( array(
+                                       'returnto' => $request->getVal( 
'returnto' ),
+                                       'returntoquery' => $request->getVal( 
'returntoquery' ),
+                               ) );
+                               $data['script'] = 'top.location.href = ' . 
Xml::encodeJsVar( $url ) . ';';
+                               break;
+                       }
+                       break;
+
+               default:
+                       $data = array(
+                               'status' => 'error',
+                               'msg' => array( 
'centralauth-centralautologin-badstate', $par ),
+                       );
+                       break;
+               }
+
+               $this->outputData( $data );
+       }
+
+       private function checkInputState( $par, $central ) {
+               global $wgCentralAuthLoginWiki;
+
+               $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-centralautologin-notposted' ),
+                       );
+               }
+
+               // Validate the state for this wiki
+               if ( $central ) {
+                       if ( wfWikiID() !== $wgCentralAuthLoginWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-badstate-central', $par ),
+                               );
+                       }
+
+                       $wikiId = $request->getVal( 'wikiid' );
+                       if ( $wikiId === $wgCentralAuthLoginWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-badwiki', $wikiId ),
+                               );
+                       }
+                       $wiki = WikiMap::getWiki( $wikiId );
+                       if ( !$wiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-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-centralautologin-corsfail' )
+                                       );
+                               }
+
+                               $response->header( 
"Access-Control-Allow-Origin: $wikiOrigin" );
+                               $response->header( 
'Access-Control-Allow-Credentials: true' );
+                               $this->getOutput()->addVaryHeader( 'Origin' );
+                       }
+               } else {
+                       if ( wfWikiID() === $wgCentralAuthLoginWiki ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-badstate-local', $par ),
+                               );
+                       }
+
+                       global $wgUser;
+                       if ( !$wgUser->isAnon() ) {
+                               return array(
+                                       'status' => 'error',
+                                       'msg' => array( 
'centralauth-centralautologin-alreadyloggedinlocally' ),
+                               );
+                       }
+               }
+
+               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()';
+
+                                       switch ( substr( $data['nextState'], 0, 
1 ) ) {
+                                       case 'C':
+                                               global $wgCentralAuthLoginWiki;
+                                               $target = 
$wgCentralAuthLoginWiki;
+
+                                       default:
+                                               $target = 
$this->getRequest()->getVal( 'wikiid', wfWikiID() );
+                                               break;
+                                       }
+                                       $body .= "\n" . Html::openElement( 
'form', array(
+                                               'method' => 'POST',
+                                               'action' => 
WikiMap::getForeignURL( $target, 'Special:CentralAutoLogin/' . 
$data['nextState'] ),
+                                       ) ) . "\n";
+                                       $body .= Html::hidden( 'form', '1' ) . 
"\n";
+                                       foreach ( array( 'oncomplete', 
'returnto', 'returntoquery' ) as $k ) {
+                                               $v = 
$this->getRequest()->getVal( $k );
+                                               if ( $v !== null ) {
+                                                       $body .= Html::hidden( 
$k, $v ) . "\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;
+                       }
+               }
+       }
+}
diff --git a/specials/SpecialCentralLogin.php b/specials/SpecialCentralLogin.php
index b4d1ab6..f01ce22 100644
--- a/specials/SpecialCentralLogin.php
+++ b/specials/SpecialCentralLogin.php
@@ -185,8 +185,8 @@
                        // The user wants an HTTP redirect link (as well as 
other links) and
                        // this is on HTTPS, so send a redirect to the success 
page in HTTP.
                        $query = array(
-                               'returnTo'      => $attempt['returnTo'],
-                               'returnToQuery' => $attempt['returnToQuery']
+                               'returnto'      => $attempt['returnTo'],
+                               'returntoquery' => $attempt['returnToQuery']
                        );
                        $url = $this->getFullTitle()->getFullUrl( $query, 
false, PROTO_HTTP );
                        $this->getOutput()->redirect( $url );
@@ -212,8 +212,8 @@
                // Show the login success page
                $form = new LoginForm( new FauxRequest() );
                $form->showReturnToPage( 'success',
-                       $this->getRequest()->getVal( 'returnTo', '' ),
-                       $this->getRequest()->getVal( 'returnToQuery', '' )
+                       $this->getRequest()->getVal( 'returnto', '' ),
+                       $this->getRequest()->getVal( 'returntoquery', '' )
                );
                $this->getOutput()->setPageTitle( $this->msg( 
'centralloginsuccesful' ) );
                // Show any icons that trigger cross-domain cookies

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

Gerrit-MessageType: merged
Gerrit-Change-Id: Ib6e9cce4fa4c5f1482c59bfac28087f558786efe
Gerrit-PatchSet: 17
Gerrit-Project: mediawiki/extensions/CentralAuth
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Alex Monk <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: CSteipp <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot

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

Reply via email to