Aaron Schulz has uploaded a new change for review.

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


Change subject: Added $wgCentralAuthLoginWiki option.
......................................................................

Added $wgCentralAuthLoginWiki option.

* This changes login to redirect to a central login wiki and back to set
  the cross-domain cookie for that wiki. This works around browsers not
  letting third party domain cookies get set directly.

Change-Id: Ie3d6523d29ed294c9d9ad44494d2e8f78ff330e9
---
M .gitignore
M CentralAuth.i18n.php
M CentralAuth.php
M CentralAuthHooks.php
M CentralAuthUser.php
A specials/SpecialCentralLogin.php
6 files changed, 268 insertions(+), 8 deletions(-)


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

diff --git a/.gitignore b/.gitignore
index 98b092a..963b2ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 *~
 *.kate-swp
 .*.swp
+/nbproject/private/
\ No newline at end of file
diff --git a/CentralAuth.i18n.php b/CentralAuth.i18n.php
index a35e38b..1ec5fca 100644
--- a/CentralAuth.i18n.php
+++ b/CentralAuth.i18n.php
@@ -400,6 +400,15 @@
 
        // Associated actions - in the sentence "You do not have permission to 
X"
        'action-centralauth-lock' => 'lock or unlock global accounts',
+
+       'centrallogin' => 'Central login',
+       'centralauth-comletelogin-legend' => 'Complete login process',
+       'centralauth-comletelogin-text' => 'Authentication of your account on 
Wikimedia Foundation sites is almost complete!
+
+       For your security, the form below is required to complete the login 
process,
+       and will submit automatically unless JavaScript is disabled in your 
browser.',
+       'centralauth-comletelogin-submit' => 'Log in now',
+
 );
 
 /** Message documentation (Message documentation)
@@ -26500,7 +26509,7 @@
        'centralauth-merge-step2-submit' => 'अभिलेखविवरणं दृढीक्रियताम्',
        'centralauth-merge-dryrun-complete' => 'विद्यमानाः सर्वे अभिलेखाः 
स्वचालितरूपेण संयोक्तुं शक्यन्ते !
 भवतः अभिलेखे न किमपि परिवर्तनं कृतम् एतावता ।',
-       'centralauth-merge-dryrun-incomplete' => 'भवान् अग्रे अनुवर्तयितुम् 
अर्हति, किन्तु केचन अभिलेखाः स्वयं दृढीकर्तुं न शक्यते, झटिति तान् संयोक्तुं न 
शक्यते । 
+       'centralauth-merge-dryrun-incomplete' => 'भवान् अग्रे अनुवर्तयितुम् 
अर्हति, किन्तु केचन अभिलेखाः स्वयं दृढीकर्तुं न शक्यते, झटिति तान् संयोक्तुं न 
शक्यते ।
 अग्रे एतान् संयोक्तुं शक्यते ।
 भवतः अभिलेखे एतावता न किमपि परिवर्तनं कृतम् ।',
        'centralauth-merge-dryrun-or' => "'''अथवा'''",
@@ -26609,7 +26618,7 @@
        'centralauth-admin-already-unmerged' => 'पूर्वमेव पृथक्कृतम् इत्यतः $1 
त्यज्यते ।',
        'centralauth-admin-unmerge-success' => '$1 
{{PLURAL:$1|अभिलेखः|अभिलेखाः}} पृथक्कृताः',
        'centralauth-admin-delete-title' => 'अभिलेखः निरस्यताम्',
-       'centralauth-admin-delete-description' => 'वैश्विकलेखायाः निष्कासनेन 
सर्वं वैश्विकं प्रशस्तं निरस्तं भविष्यति, स्थानीयलेखाः वियुक्ताः भविष्यन्ति । 
तथा च तत् वैश्विकं नाम अन्यः उपयोक्तुम् अर्हति । 
+       'centralauth-admin-delete-description' => 'वैश्विकलेखायाः निष्कासनेन 
सर्वं वैश्विकं प्रशस्तं निरस्तं भविष्यति, स्थानीयलेखाः वियुक्ताः भविष्यन्ति । 
तथा च तत् वैश्विकं नाम अन्यः उपयोक्तुम् अर्हति ।
 सर्वाः स्थानीयलेखाः शिष्यन्ते ।
 संयोगात् पूर्वं सृष्टाः स्थानीयलेखानां कूटशब्दाः पूर्ववदेव शिष्यन्ते ।',
        'centralauth-admin-delete-button' => 'अयम् अभिलेखः विलोप्यताम्',
@@ -26700,7 +26709,7 @@
        'centralauth-rightslog-set-optout' => 'आप्ट्-औट् आधारीकृतम्',
        'autologin' => 'स्वचालितप्रवेशस्य कारणेन',
        'centralauth-autologin-desc' => 'विकिमाध्यमेन इदं विशिष्टं पृष्ठम् 
अभ्यन्तरे उपयुज्यते ।
-यदा भवान् [[Special:UserLogin|प्रविशति]], तदा केन्द्रीयप्रवेशनव्यवस्था भवतः 
जालगवेशकं सर्वेभ्यः सम्बद्धक्षेत्रेभ्यः चित्रस्य अनुबन्धान् 
+यदा भवान् [[Special:UserLogin|प्रविशति]], तदा केन्द्रीयप्रवेशनव्यवस्था भवतः 
जालगवेशकं सर्वेभ्यः सम्बद्धक्षेत्रेभ्यः चित्रस्य अनुबन्धान्
 उपयुज्य एतत् पृष्ठम् आनेतुम् आदिशति ।
 यतः प्रमाणं विना भवान् इदं पृष्ठं प्रार्थयते, अतः कार्यं न सिद्ध्यति ।',
        'globalgroupmembership' => 'वैश्विकगणेषु सदस्यत्वम्',
diff --git a/CentralAuth.php b/CentralAuth.php
index bfd06d9..4920dd1 100644
--- a/CentralAuth.php
+++ b/CentralAuth.php
@@ -68,6 +68,21 @@
 $wgCentralAuthCookies = false;
 
 /**
+ * Database name of a central login wiki. This is an alternative to directly 
setting
+ * cross-domain cookies for each wiki in $wgCentralAuthAutoLoginWikis. If set, 
a single
+ * 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.
+ * 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.
+ *
+ * This requires $wgCentralAuthCookies.
+ */
+$wgCentralAuthLoginWiki = false;
+
+/**
  * Domain to set global cookies for.
  * For instance, '.wikipedia.org' to work on all wikipedia.org subdomains
  * instead of just the current one.
@@ -152,6 +167,7 @@
  */
 $caBase = __DIR__;
 $wgAutoloadClasses['SpecialCentralAuth'] = 
"$caBase/specials/SpecialCentralAuth.php";
+$wgAutoloadClasses['SpecialCentralLogin'] = 
"$caBase/specials/SpecialCentralLogin.php";
 $wgAutoloadClasses['SpecialMergeAccount'] = 
"$caBase/specials/SpecialMergeAccount.php";
 $wgAutoloadClasses['SpecialGlobalUsers'] = 
"$caBase/specials/SpecialGlobalUsers.php";
 $wgAutoloadClasses['SpecialMultiLock'] = 
"$caBase/specials/SpecialMultiLock.php";
@@ -231,6 +247,7 @@
 $wgGroupPermissions['*']['centralauth-merge'] = true;
 
 $wgSpecialPages['CentralAuth'] = 'SpecialCentralAuth';
+$wgSpecialPages['CentralLogin'] = 'SpecialCentralLogin';
 $wgSpecialPages['AutoLogin'] = 'SpecialAutoLogin';
 $wgSpecialPages['MergeAccount'] = 'SpecialMergeAccount';
 $wgSpecialPages['GlobalGroupMembership'] = 'SpecialGlobalGroupMembership';
diff --git a/CentralAuthHooks.php b/CentralAuthHooks.php
index 3733186..6068579 100644
--- a/CentralAuthHooks.php
+++ b/CentralAuthHooks.php
@@ -142,6 +142,7 @@
        /**
         * @param $user User
         * @param $inject_html string
+        * @param $form LoginForm
         * @return bool
         */
        static function onUserLoginComplete( &$user, &$inject_html ) {
@@ -152,11 +153,13 @@
                }
 
                $centralUser = CentralAuthUser::getInstance( $user );
-
                if ( !$centralUser->exists() || !$centralUser->isAttached() ) {
                        $centralUser->deleteGlobalCookies();
                        return true;
                }
+
+               // On central login wiki
+               self::doCentralWikiLoginRedirect( $user, $form );
 
                // On other domains
                global $wgCentralAuthAutoLoginWikis;
@@ -200,6 +203,74 @@
                return true;
        }
 
+       protected static function doCentralWikiLoginRedirect( User $user ) {
+               global $wgCentralAuthLoginWiki, $wgMemc;
+
+               if ( !$wgCentralAuthLoginWiki || defined( 'MW_API' ) ) {
+                       return true;
+               }
+
+               // Get the global user for this user
+               $caUser = CentralAuthUser::getInstance( $user );
+               if ( !$caUser->getId() ) {
+                       return true; // user isn't unified?
+               }
+
+               // Check that this is actually for a special login page view
+               $context = RequestContext::getMain();
+               if ( $context->getTitle()->isSpecial( 'Userlogin' ) ) {
+                       $url = WikiMap::getForeignURL( $wgCentralAuthLoginWiki, 
'Special:CentralLogin/start' );
+                       if ( strlen( $url ) ) {
+                               $request = $context->getRequest();
+                               // User will be redirected to 
Special:CentralLogin/start (central wiki),
+                               // POST to Special:CentralAutoLogin (this wiki) 
and sent to the "returnto".
+                               // Sanity check that "returnto" is not one of 
the central login pages. If it
+                               // is, then clear the "returnto" options 
(LoginForm will use the main page).
+                               $returnTo = $request->getVal( 'returnto', '' );
+                               $returnToQuery = $request->getVal( 
'returntoquery', '' );
+                               $returnToTitle = Title::newFromText( $returnTo 
);
+                               if ( $returnToTitle && 
$returnToTitle->isSpecial( 'CentralLogin' ) ) {
+                                       $returnTo = '';
+                                       $returnToQuery = '';
+                               }
+
+                               // When POSTs triggered from 
Special:CentralLogin/start are sent back to
+                               // this wiki, the token will be checked to see 
if it was signed with this.
+                               // This is needed as Special:CentralLogin/start 
only takes a token argument
+                               // and we need to make sure an agent requesting 
such a URL actually initiated
+                               // the login request that spawned that token 
server-side.
+                               $secret = MWCryptRand::generateHex( 32 );
+                               
$_SESSION['CentralAuth:autologin:current-attempt'] = array(
+                                       'secret'        => $secret,
+                                       'remember'      => $request->getCheck( 
'wpRemember' ),
+                                       'returnTo'      => $returnTo,
+                                       'returnToQuery' => $returnToQuery
+                               );
+
+                               // Create a new token to pass to 
Special:CentralLogin/start (central wiki)
+                               $token = MWCryptRand::generateHex( 32 );
+                               $key = CentralAuthUser::memcKey( 
'central-login-start-token', $token );
+                               $data = array(
+                                       'secret'        => $secret,
+                                       'name'          => $caUser->getName(),
+                                       'guid'          => $caUser->getId(),
+                                       'wikiId'        => wfWikiId(),
+                                       'stickHTTPS'    => $request->getCheck( 
'wpStickHTTPS' ),
+                                       'returnTo'      => $returnTo,
+                                       'returnToQuery' => $returnToQuery
+                               );
+                               $wgMemc->set( $key, $data, 30 );
+
+                               $url = wfAppendQuery( $url, array( 'token' => 
$token ) );
+                               $context->getOutput()->redirect( $url );
+                       } else {
+                               wfDebug( "Unable to create 
Special:CentralLogin/start via WikiMap." );
+                       }
+               }
+
+               return true;
+       }
+
        /**
         * @param $user User
         * @param $result
@@ -222,8 +293,13 @@
                        $userName = $_COOKIE["{$prefix}User"];
                        $token = $_COOKIE["{$prefix}Token"];
                } elseif ( (bool)( $session = CentralAuthUser::getSession() ) ) 
{
-                       $token = $session['token'];
-                       $userName = $session['user'];
+                       if ( isset( $session['pending_name'] ) || isset( 
$session['pending_guid'] ) ) {
+                               wfDebug( __METHOD__ . ": unintialized 
session\n" );
+                               return true;
+                       } else {
+                               $token = $session['token'];
+                               $userName = $session['user'];
+                       }
                } else {
                        wfDebug( __METHOD__ . ": no token or session\n" );
                        return true;
diff --git a/CentralAuthUser.php b/CentralAuthUser.php
index e776703..d0cc6ee 100644
--- a/CentralAuthUser.php
+++ b/CentralAuthUser.php
@@ -173,7 +173,7 @@
                // since we're caching it.
                $dbr = self::getCentralDB();
 
-               $row = $dbr->selectRow( 
+               $row = $dbr->selectRow(
                        array( 'globaluser', 'localuser' ),
                        array(
                                'gu_id', 'lu_wiki', 'gu_salt', 'gu_password', 
'gu_auth_token',
@@ -1964,7 +1964,10 @@
        }
 
        /**
-        * Set a global cookie that auto-authenticates the user on other wikis
+        * Set a global cookie that auto-authenticates the user on other wikis.
+        * This also destroys and "pending_name"/"pending_guid" keys in the 
session,
+        * which exist when a partially authenticated stub session is created.
+        *
         * Called on login.
         *
         * @param $remember Bool|User
diff --git a/specials/SpecialCentralLogin.php b/specials/SpecialCentralLogin.php
new file mode 100644
index 0000000..3e83142
--- /dev/null
+++ b/specials/SpecialCentralLogin.php
@@ -0,0 +1,154 @@
+<?php
+
+// @FIXME: replace exceptions with internationalized errors
+class SpecialCentralLogin extends UnlistedSpecialPage {
+       function __construct() {
+               parent::__construct( 'CentralLogin' );
+       }
+
+       function execute( $subpage ) {
+               $this->setHeaders();
+
+               $token = $this->getRequest()->getVal( 'token' );
+
+               $this->getOutput()->disallowUserJs(); // just in case...
+
+               if ( $subpage === 'start' ) {
+                       $this->doLoginStart( $token );
+               } elseif ( $subpage === 'complete' ) {
+                       if ( $this->getRequest()->wasPosted() ) {
+                               $this->doLoginComplete( $token );
+                       } else {
+                               throw new MWException( "Requests to this page 
must use HTTP POST." );
+                       }
+               }
+       }
+
+       protected function doLoginStart( $token ) {
+               global $wgMemc;
+
+               $key = CentralAuthUser::memcKey( 'central-login-start-token', 
$token );
+
+               $info = $wgMemc->get( $key );
+               if ( !is_array( $info ) ) {
+                       throw new MWException( "Key for token '$token' expired 
or is invalid." );
+               }
+
+               $caUser = new CentralAuthUser( $info['name'] );
+               if ( !$caUser->exists() ) {
+                       throw new MWException( "Global user '{$info['name']}' 
does not exist." );
+               } elseif ( $caUser->getId() !== $info['guid'] ) {
+                       throw new MWException( "Global user does not have ID 
'{$info['guid']}'." );
+               }
+
+               $session = CentralAuthUser::getSession();
+               if ( $session !== array() ) {
+                       if ( isset( $session['pending_name'] )
+                               && $session['pending_name'] !== 
$caUser->getName() )
+                       {
+                               throw new MWException( "Token user name does 
not match session's pending_name." );
+                               return;
+                       } elseif ( isset( $session['name'] )
+                               && $session['name'] !== $caUser->getName() )
+                       {
+                               throw new MWException( "Token user name does 
not match session's user name." );
+                               return;
+                       } elseif ( isset( $session['name'] ) ) { // already 
initialized session
+                               $url = WikiMap::getForeignURL( $info['wikiId'], 
'Special:Userlogin' );
+                               if ( strlen( $url ) ) {
+                                       $url = wfAppendQuery( $url, array(
+                                               'showSuccess'   => 1,
+                                               'returnTo'      => 
$info['returnTo'],
+                                               'returnToQuery' => 
$info['returnToQuery'],
+                                               'wpStickHTTPS'  => 
$info['stickHTTPS']
+                                       ) );
+                                       $this->getOutput()->redirect( $url );
+                               }
+                               return;
+                       }
+               }
+
+               // Delete the temporary token
+               $wgMemc->delete( $key );
+
+               // Start an unusable placeholder session stub
+               $newSessionId = CentralAuthUser::setSession( array(
+                       'pending_name' => $caUser->getName(),
+                       'pending_guid' => $caUser->getId()
+               ) );
+
+               // Create a new token to pass to Special:CentralLogin/complete 
(local wiki)
+               $token = MWCryptRand::generateHex( 32 );
+               $key = CentralAuthUser::memcKey( 
'central-login-complete-token', $token );
+               $data = array(
+                       'sessionId' => $newSessionId,
+                       'signature' => hash_hmac( 'sha1', $newSessionId, 
$info['secret'] )
+               );
+               $wgMemc->set( $key, $data, 60 );
+
+               $url = WikiMap::getForeignURL( $info['wikiId'], 
'Special:CentralLogin/complete' );
+               if ( !strlen( $url ) ) {
+                       throw new MWException( 'Could not generate form target 
URL.' );
+               }
+
+               // @TODO: missing messages
+               $this->getOutput()->addHtml(
+                       Html::rawElement( 'p',
+                               null, $this->msg( 
'centralauth-comletelogin-text' )->parse() ) .
+                       Xml::openElement( 'form',
+                               array( 'method' => 'post', 'action' => $url, 
'id' => 'completeloginform' ) ) .
+                       Html::hidden( 'token', $token ) .
+                       Xml::openElement( 'fieldset' ) .
+                       Html::rawElement( 'legend',
+                               null, $this->msg( 
'centralauth-comletelogin-legend' )->parse() ) .
+                       Xml::submitButton( $this->msg( 
'centralauth-comletelogin-submit' )->text() ) .
+                       Xml::closeElement( 'fieldset' ) .
+                       Xml::closeElement( 'form' ) . "\n"
+               );
+
+               $this->getOutput()->addInlineScript(
+                       
"document.getElementById('completeloginform').submit();" );
+       }
+
+       protected function doLoginComplete( $token ) {
+               global $wgMemc;
+
+               $request = $this->getRequest();
+
+               $key = CentralAuthUser::memcKey( 
'central-login-complete-token', $token );
+               $skey = 'CentralAuth:autologin:current-attempt';
+
+               $attemptInfo = $request->getSessionData( 
'CentralAuth:autologin:current-attempt' );
+               if ( !isset( $attemptInfo['secret'] ) ) {
+                       throw new MWException( "No active login attempt is in 
progress for the session." );
+               }
+
+               $info = $wgMemc->get( $key );
+               if ( !is_array( $info ) ) {
+                       throw new MWException( "Key for token '$token' expired 
or is invalid." );
+               }
+
+               $hash = hash_hmac( 'sha1', $info['sessionId'], 
$_SESSION[$skey]['secret'] );
+               if ( $hash !== $info['signature'] ) {
+                       throw new MWException(
+                               "The token does not have the signature of the 
current login attempt." );
+               }
+
+               $caUser = new CentralAuthUser( $request->getSessionData( 
'wsUserName' ) );
+               if ( !$caUser->getId() ) {
+                       throw new MWException( "The user account is not 
attached." );
+               }
+
+               // Fully initialize the central user session.
+               // This lets User::loadFromSession to initialize the User object
+               // from the local session now that the global session is 
complete.
+               $caUser->setGlobalCookies( $_SESSION[$skey]['remember'] );
+
+               // Remove the "current login attempt" information
+               $request->setSessionData( $skey, null );
+
+               // Redirect the user to the login success page
+               $form = new LoginForm( new FauxRequest() );
+               $form->executeSuccessRedirect( $attemptInfo['returnTo'], 
$attemptInfo['returnToQuery'] );
+       }
+}

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

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

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

Reply via email to