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