r33061 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r33060‎ | r33061 | r33062 >
Date:05:59, 10 April 2008
Author:werdna
Status:old
Tags:
Comment:
Log users into all wikis when they log into one wiki. Loads images from a list of wikis set in $wgCentralAuthAutoLoginWikis to set a cookie. Cookies apply to $wgCentralAuthCookieDomains. Can be configured on Wikimedia by selecting one db from each second-level domain, and setting the cookie domains to *.sld.org.
Modified paths:
  • /trunk/extensions/CentralAuth/1x1.png (added) (history)
  • /trunk/extensions/CentralAuth/CentralAuth.php (modified) (history)
  • /trunk/extensions/CentralAuth/CentralAuthUser.php (modified) (history)
  • /trunk/extensions/CentralAuth/SpecialAutoLogin.php (added) (history)
  • /trunk/extensions/CentralAuth/central-auth.sql (modified) (history)

Diff [purge]

Index: trunk/extensions/CentralAuth/CentralAuthUser.php
@@ -60,12 +60,12 @@
6161 private function loadState() {
6262 if( !isset( $this->mGlobalId ) ) {
6363 global $wgDBname;
64 - $dbr = self::getCentralDB();
 64+ $dbr = self::getCentralSlaveDB();
6565 $globaluser = self::tableName( 'globaluser' );
6666 $localuser = self::tableName( 'localuser' );
6767
6868 $sql =
69 - "SELECT gu_id, lu_dbname
 69+ "SELECT gu_id, lu_dbname, gu_salt, gu_password,gu_authtoken,gu_locked,gu_hidden,gu_registration
7070 FROM $globaluser
7171 LEFT OUTER JOIN $localuser
7272 ON gu_name=lu_name
@@ -78,6 +78,12 @@
7979 if( $row ) {
8080 $this->mGlobalId = intval( $row->gu_id );
8181 $this->mIsAttached = ($row->lu_dbname !== null);
 82+ $this->mSalt = $row->gu_salt;
 83+ $this->mPassword = $row->gu_password;
 84+ $this->mAuthToken = $row->gu_authtoken;
 85+ $this->mLocked = $row->gu_locked;
 86+ $this->mHidden = $row->gu_hidden;
 87+ $this->mRegistration = $row->gu_registration;
8288 } else {
8389 $this->mGlobalId = 0;
8490 $this->mIsAttached = false;
@@ -100,6 +106,27 @@
101107 $this->loadState();
102108 return $this->mIsAttached;
103109 }
 110+
 111+ /**
 112+ * Return the password hash and salt.
 113+ * @return array( salt, hash )
 114+ */
 115+ public function getPasswordHash() {
 116+ $this->loadState();
 117+ return array( $this->mSalt, $this->mPassword );
 118+ }
 119+
 120+ /**
 121+ * Return the global-login token for this account.
 122+ */
 123+ public function getAuthToken() {
 124+ $this->loadState();
 125+
 126+ if (!$this->mAuthToken) {
 127+ $this->resetAuthToken();
 128+ }
 129+ return $this->mAuthToken;
 130+ }
104131
105132 /**
106133 * Check whether a global user account for this name exists yet.
@@ -116,41 +143,27 @@
117144 }
118145
119146 /**
120 - * Lazy-load misc properties that may be used at times
121 - */
122 - private function loadProperties() {
123 - if( !isset( $this->mProperties ) ) {
124 - $dbw = self::getCentralDB();
125 - $row = $dbw->selectRow( self::tableName( 'globaluser' ),
126 - array( 'gu_locked', 'gu_hidden', 'gu_registration' ),
127 - array( 'gu_name' => $this->mName ),
128 - __METHOD__ );
129 - $this->mProperties = $row;
130 - }
131 - }
132 -
133 - /**
134147 * @return bool
135148 */
136149 public function isLocked() {
137 - $this->loadProperties();
138 - return (bool)$this->mProperties->gu_locked;
 150+ $this->loadState();
 151+ return (bool)$this->mLocked;
139152 }
140153
141154 /**
142 - * @return bool
 155+ * @return bool
143156 */
144157 public function isHidden() {
145 - $this->loadProperties();
146 - return (bool)$this->mProperties->gu_hidden;
 158+ $this->loadState();
 159+ return (bool)$this->mHidden;
147160 }
148161
149162 /**
150163 * @return string timestamp
151164 */
152165 public function getRegistration() {
153 - $this->loadProperties();
154 - return wfTimestamp( TS_MW, $this->mProperties->gu_registration );
 166+ $this->loadState();
 167+ return wfTimestamp( TS_MW, $this->mRegistration );
155168 }
156169
157170 private function lazyMigrate() {
@@ -677,38 +690,46 @@
678691 $this->resetState();
679692 }
680693 }
681 -
 694+
682695 /**
683 - * Attempt to authenticate the global user account with the given password
684 - * @param string $password
685 - * @return string status, one of: "ok", "no user", "locked", or "bad password".
686 - * @todo Currently only the "ok" result is used (i.e. either use, or return a bool).
 696+ * If the user provides the correct password, would we let them log in?
 697+ * This encompasses checks on missing and locked accounts, at present.
 698+ * @return string status, one of: "no user", "locked"
687699 */
688 - public function authenticate( $password ) {
 700+ public function canAuthenticate() {
689701 $this->lazyMigrate();
690702
691 - $dbw = self::getCentralDB();
692 - $row = $dbw->selectRow( self::tableName( 'globaluser' ),
693 - array( 'gu_salt', 'gu_password', 'gu_locked' ),
694 - array( 'gu_id' => $this->getId() ),
695 - __METHOD__ );
696 -
697 - if( !$row ) {
 703+ if( !$this->getId() ) {
698704 wfDebugLog( 'CentralAuth',
699705 "authentication for '$this->mName' failed due to missing account" );
700706 return "no user";
701707 }
702708
703 - $salt = $row->gu_salt;
704 - $crypt = $row->gu_password;
705 - $locked = $row->gu_locked;
 709+ list($salt,$crypt) = $this->getPasswordHash();
 710+ $locked = $this->isLocked();
706711
707712 if( $locked ) {
708713 wfDebugLog( 'CentralAuth',
709714 "authentication for '$this->mName' failed due to lock" );
710715 return "locked";
711716 }
 717+
 718+ return true;
 719+ }
712720
 721+ /**
 722+ * Attempt to authenticate the global user account with the given password
 723+ * @param string $password
 724+ * @return string status, one of: "ok", "no user", "locked", or "bad password".
 725+ * @todo Currently only the "ok" result is used (i.e. either use, or return a bool).
 726+ */
 727+ public function authenticate( $password ) {
 728+ if (($ret = $this->canAuthenticate()) !== true) {
 729+ return $ret;
 730+ }
 731+
 732+ list( $salt, $crypt ) = $this->getPasswordHash();
 733+
713734 if( $this->matchHash( $password, $salt, $crypt ) ) {
714735 wfDebugLog( 'CentralAuth',
715736 "authentication for '$this->mName' succeeded" );
@@ -719,6 +740,23 @@
720741 return "bad password";
721742 }
722743 }
 744+
 745+ /**
 746+ * Attempt to authenticate the global user account with the given global authtoken
 747+ * @param string $token
 748+ * @return string status, one of: "ok", "no user", "locked", or "bad token"
 749+ */
 750+ public function authenticateWithToken( $token ) {
 751+ if (($ret = $this->canAuthenticate()) !== true) {
 752+ return $ret;
 753+ }
 754+
 755+ if ($this->validateAuthToken( $token ) ) {
 756+ return "ok";
 757+ } else {
 758+ return "bad token";
 759+ }
 760+ }
723761
724762 /**
725763 * @param $plaintext User-provided password plaintext.
@@ -1095,7 +1133,78 @@
10961134
10971135 wfDebugLog( 'CentralAuth',
10981136 "Set global password for '$this->mName'" );
 1137+
 1138+ // Reset the auth token.
 1139+ $this->resetAuthToken();
 1140+ $this->setGlobalCookies();
10991141 return true;
11001142 }
11011143
 1144+ /**
 1145+ * Set a global cookie that auto-authenticates the user on other wikis
 1146+ * Called on login.
 1147+ */
 1148+ function setGlobalCookies($localUser) {
 1149+ global $wgCentralAuthCookiePrefix,$wgCentralAuthCookieDomain,$wgCookieSecure,$wgCookieExpiration;
 1150+
 1151+ $exp = time() + $wgCookieExpiration;
 1152+
 1153+ $global_session = array();
 1154+
 1155+ $global_session['user'] = $this->mName;
 1156+ setcookie( $wgCentralAuthCookiePrefix.'User', $this->mName, $exp, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1157+ $global_session['token'] = $this->getAuthToken();
 1158+ $global_session['expiry'] = time() + 86400;
 1159+
 1160+ if ($localUser->getOption('rememberpassword') == 1) {
 1161+ setcookie( $wgCentralAuthCookiePrefix.'Token', $this->getAuthToken(), $exp, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1162+ } else {
 1163+ setcookie( $wgCentralAuthCookiePrefix.'Token', '', time() - 86400 );
 1164+ }
 1165+
 1166+ // Make up a session id.
 1167+ $session_id = md5( $exp . mt_rand( 0, 0x7fffffff ) . $this->getId() );
 1168+ // Store the session in memcached
 1169+ global $wgMemc;
 1170+ $wgMemc->set( 'centralauth_session_'.$session_id, serialize($global_session), 86400 );
 1171+
 1172+ setcookie( $wgCentralAuthCookiePrefix.'Session', $session_id, time() + 86400, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1173+ }
 1174+
 1175+ /**
 1176+ * Delete global cookies which auto-authenticate the user on other wikis.
 1177+ * Called on logout.
 1178+ */
 1179+ function deleteGlobalCookies() {
 1180+ global $wgCentralAuthCookiePrefix,$wgCentralAuthCookieDomain,$wgCookieSecure;
 1181+
 1182+ $exp = time() - 86400;
 1183+
 1184+ setcookie( $wgCentralAuthCookiePrefix.'User', '', $exp, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1185+ setcookie( $wgCentralAuthCookiePrefix.'Token', '', $exp, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1186+ setcookie( $wgCentralAuthCookiePrefix.'Session', '', $exp, '/', $wgCentralAuthCookieDomain, $wgCookieSecure );
 1187+
 1188+ $this->resetAuthToken();
 1189+ }
 1190+
 1191+ /**
 1192+ * Check a global auth token against the one we know of in the database.
 1193+ */
 1194+ function validateAuthToken( $token ) {
 1195+ return ($token == $this->getAuthToken());
 1196+ }
 1197+
 1198+ /**
 1199+ * Generate a new random auth token, and store it in the database.
 1200+ * Should be called as often as possible, to the extent that it will
 1201+ * not randomly log users out (so on logout, as is done currently, is a good time).
 1202+ */
 1203+ function resetAuthToken() {
 1204+ // Generate a random token.
 1205+ $this->mAuthToken = md5( mt_rand( 0, 0x7fffffff ) . $this->getId() );
 1206+
 1207+ // Save it.
 1208+ $dbw = self::getCentralDB();
 1209+ $dbw->update( self::tableName( 'globaluser' ), array( 'gu_authtoken' => $this->mAuthToken ), array( 'gu_id' => $this->getId() ), __METHOD__ );
 1210+ }
11021211 }
Index: trunk/extensions/CentralAuth/CentralAuth.php
@@ -38,6 +38,27 @@
3939 */
4040 $wgCentralAuthDryRun = false;
4141
 42+/**
 43+ * Domain to set global cookies for.
 44+ */
 45+$wgCentralAuthCookieDomains = $wgServer;
 46+
 47+/**
 48+ * Prefix for CentralAuth cookies.
 49+ */
 50+$wgCentralAuthCookiePrefix = 'centralauth_';
 51+
 52+/**
 53+ * Wikis to automatically log into when this one is logged into.
 54+ * Done by loading a 1x1 image from Special:AutoLogin on that wiki.
 55+ */
 56+$wgCentralAuthAutoLoginWikis = array();
 57+
 58+/**
 59+ * Prefix for CentralAuth global auto-authentication cookies
 60+ */
 61+ $wgCentralAuthCookiePrefix = 'centralauth';
 62+
4263 $wgExtensionCredits['specialpage'][] = array(
4364 'name' => 'Central Auth',
4465 'url' => 'http://www.mediawiki.org/wiki/Extension:CentralAuth',
@@ -57,12 +78,17 @@
5879 $wgAutoloadClasses['CentralAuthPlugin'] = "$caBase/CentralAuthPlugin.php";
5980 $wgAutoloadClasses['WikiMap'] = "$caBase/WikiMap.php";
6081 $wgAutoloadClasses['WikiReference'] = "$caBase/WikiMap.php";
 82+$wgAutoloadClasses['SpecialAutoLogin'] = "$caBase/SpecialAutoLogin.php";
6183 $wgExtensionMessagesFiles['SpecialCentralAuth'] = "$caBase/CentralAuth.i18n.php";
6284
6385 $wgHooks['AuthPluginSetup'][] = 'wfSetupCentralAuthPlugin';
6486 $wgHooks['AddNewAccount'][] = 'wfCentralAuthAddNewAccount';
6587 $wgHooks['PreferencesUserInformationPanel'][] = 'wfCentralAuthInformationPanel';
6688 $wgHooks['AbortNewAccount'][] = 'wfCentralAuthAbortNewAccount';
 89+$wgHooks['UserLoginComplete'][] = 'wfCentralAuthUserLoginComplete';
 90+$wgHooks['AutoAuthenticate'][] = 'wfCentralAuthAutoAuthenticate';
 91+$wgHooks['UserLogout'][] = 'wfCentralAuthLogout';
 92+$wgHooks['UserLogoutComplete'][] = 'wfCentralAuthLogoutComplete';
6793
6894 // For interaction with the Special:Renameuser extension
6995 $wgHooks['RenameUserAbort'][] = 'wfCentralAuthRenameUserAbort';
@@ -72,6 +98,7 @@
7399 $wgGroupPermissions['*']['centralauth-merge'] = true;
74100
75101 $wgSpecialPages['CentralAuth'] = 'SpecialCentralAuth';
 102+$wgSpecialPages['AutoLogin'] = 'SpecialAutoLogin';
76103 $wgSpecialPages['MergeAccount'] = 'SpecialMergeAccount';
77104
78105 function wfSetupCentralAuthPlugin( &$auth ) {
@@ -199,3 +226,107 @@
200227 return true;
201228 }
202229
 230+function wfCentralAuthUserLoginComplete( &$user, &$inject_html ) {
 231+ $centralUser = new CentralAuthUser( $user->getName() );
 232+
 233+ if ($centralUser->exists()) {
 234+ $centralUser->setGlobalCookies($user);
 235+ } else {
 236+ return;
 237+ }
 238+
 239+ // On other wikis
 240+ global $wgCentralAuthAutoLoginWikis;
 241+
 242+ $inject_html .= Xml::openElement( 'p' );
 243+
 244+ foreach( $wgCentralAuthAutoLoginWikis as $dbname ) {
 245+ $wiki = WikiMap::byDatabase( $dbname );
 246+ $url = $wiki->getUrl( 'Special:AutoLogin' );
 247+
 248+ $querystring = 'user=' . urlencode( $user->getName() );
 249+ $querystring .= '&token=' . $centralUser->getAuthToken();
 250+ $querystring .= '&remember=' . $user->getOption( 'rememberpassword' );
 251+
 252+ if (strpos($url, '?') > 0) {
 253+ $url .= "&$querystring";
 254+ } else {
 255+ $url .= "?$querystring";
 256+ }
 257+
 258+ $inject_html .= Xml::element( 'img', array( 'src' => $url ) );
 259+ }
 260+
 261+ $inject_html .= Xml::closeElement( 'p' );
 262+
 263+ return true;
 264+}
 265+
 266+function wfCentralAuthAutoAuthenticate( &$user ) {
 267+ global $wgCentralAuthCookiePrefix;
 268+ $prefix = $wgCentralAuthCookiePrefix;
 269+
 270+ if (isset($_COOKIE["{$prefix}User"]) && isset($_COOKIE["{$prefix}Token"])) {
 271+ list ($username, $token) = array( $_COOKIE["{$prefix}User"], $_COOKIE["{$prefix}Token"] );
 272+ $centralUser = new CentralAuthUser( $username );
 273+
 274+ if ($centralUser->authenticateWithToken( $token ) == 'ok' && $centralUser->isAttached()) {
 275+ // Auth OK.
 276+ $user = User::newFromName( $username );
 277+ }
 278+ } elseif (isset($_COOKIE["{$prefix}Session"])) {
 279+ $session_id = $_COOKIE["{$prefix}Session"];
 280+
 281+ global $wgMemc;
 282+ $global_session = unserialize($wgMemc->get( "centralauth_session_$session_id" ));
 283+
 284+ $token = $global_session['token'];
 285+ $username = $global_session['user'];
 286+
 287+ if ($global_session['expiry'] < time()) {
 288+ return true; // Session has expired. Don't let it be logged-in with.
 289+ }
 290+
 291+ $centralUser = new CentralAuthUser( $username );
 292+
 293+ if ($centralUser->authenticateWithToken( $token ) == 'ok' && $centralUser->isAttached()) {
 294+ // Auth OK.
 295+ $user = User::newFromName( $username );
 296+ }
 297+ }
 298+
 299+ return true;
 300+}
 301+
 302+function wfCentralAuthLogout( &$user ) {
 303+ $centralUser = new CentralAuthUser( $user->getName() );
 304+
 305+ if ($centralUser->exists()) {
 306+ $centralUser->deleteGlobalCookies();
 307+ }
 308+
 309+ return true;
 310+}
 311+
 312+function wfCentralAuthLogoutComplete( &$user, &$inject_html ) {
 313+ // Generate the images
 314+ global $wgCentralAuthAutoLoginWikis;
 315+
 316+ $inject_html .= Xml::openElement( 'p' );
 317+
 318+ foreach( $wgCentralAuthAutoLoginWikis as $dbname ) {
 319+ $wiki = WikiMap::byDatabase( $dbname );
 320+ $url = $wiki->getUrl( 'Special:AutoLogin' );
 321+
 322+ if (strpos($url, '?') > 0) {
 323+ $url .= '&logout=1';
 324+ } else {
 325+ $url .= '?logout=1';
 326+ }
 327+
 328+ $inject_html .= Xml::element( 'img', array( 'src' => $url, alt => '' ) );
 329+ }
 330+
 331+ $inject_html .= Xml::closeElement( 'p' );
 332+ return true;
 333+}
\ No newline at end of file
Index: trunk/extensions/CentralAuth/central-auth.sql
@@ -80,6 +80,9 @@
8181 -- Random key for password resets
8282 gu_password_reset_key tinyblob,
8383 gu_password_reset_expiration varchar(14) binary,
 84+
 85+ -- Random key for crosswiki authentication tokens
 86+ gu_auth_token varchar(32) binary,
8487
8588 primary key (gu_id),
8689 unique key (gu_name),
Index: trunk/extensions/CentralAuth/SpecialAutoLogin.php
@@ -0,0 +1,59 @@
 2+<?
 3+if (!defined('MEDIAWIKI')) {
 4+ die('CentralAuth');
 5+}
 6+
 7+/**
 8+ * Unlisted Special page to set requisite cookies for being logged into this wiki.
 9+ *
 10+ * @addtogroup Extensions
 11+ */
 12+
 13+ global $IP;
 14+
 15+require_once( "$IP/includes/StreamFile.php" );
 16+
 17+class SpecialAutoLogin extends UnlistedSpecialPage
 18+{
 19+ function SpecialAutoLogin() {
 20+ SpecialPage::SpecialPage('AutoLogin');
 21+ }
 22+
 23+ function execute() {
 24+ global $wgRequest,$wgOut,$wgUser;
 25+
 26+ $username = $wgRequest->getVal( 'user' );
 27+ $token = $wgRequest->getVal('token');
 28+ $remember = $wgRequest->getBool( 'remember' );
 29+ $logout = $wgRequest->getBool( 'logout' );
 30+
 31+ if ((strlen($username) == 0 || strlen($token) == 0) && !$logout) {
 32+ $wgOut->addWikitext( 'AutoLogin' );
 33+ return;
 34+ }
 35+
 36+ if ($logout == true) {
 37+ $centralUser = new CentralAuthUser( $wgUser->getName() );
 38+
 39+ if ($centralUser->getId) {
 40+ $centralUser->deleteGlobalCookies();
 41+ }
 42+ } else {
 43+ $centralUser = new CentralAuthUser( $username );
 44+
 45+ $login_result = $centralUser->authenticateWithToken( $token );
 46+
 47+ if ($login_result == 'ok' && $centralUser->isAttached()) {
 48+ // Auth OK.
 49+ $user = User::newFromName( $username );
 50+ $user->setOption( 'rememberpassword', $remember );
 51+
 52+ $centralUser->setGlobalCookies($user);
 53+ }
 54+ }
 55+
 56+ wfStreamFile( dirname(__FILE__).'/1x1.png' );
 57+
 58+ $wgOut->disable();
 59+ }
 60+}
\ No newline at end of file
Property changes on: trunk/extensions/CentralAuth/SpecialAutoLogin.php
___________________________________________________________________
Added: svn:eol-style
161 + native
Index: trunk/extensions/CentralAuth/1x1.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/CentralAuth/1x1.png
___________________________________________________________________
Added: svn:mime-type
262 + image/png

Status & tagging log