r53497 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r53496‎ | r53497 | r53498 >
Date:22:02, 19 July 2009
Author:simetrical
Status:ok (Comments)
Tags:todo 
Comment:
Add experimental new auth framework, ExternalAuth

This should not affect any existing behavior. (Except that it reorders
some error conditions in attemptAutoCreate(), but probably no one cares
about that.) It adds a new database table, but it will be unused unless
you enable external authentication.

An outline of the rationale for this system, and the design planning, is
at <http://www.mediawiki.org/wiki/ExternalAuth&gt;. Essentially,
AuthPlugin puts too much of a burden on plugin authors, requiring them
to write a lot of policy logic instead of just handling the actual
interface to the external user database. This system uses a standard
framework to decide policy questions, and auth plugins only need to
provide some low-level, clearly-specified data.

There are lots of features still missing, marked in the code, but basic
functionality is present. The commit includes initial support for one
type of external authentication, the forum software vBulletin (which I
happen to know well, and want to integrate with my MediaWiki).

I'm encouraging the inclusion of ExternalAuth plugins in core because in
this framework, the amount of code required to add an additional backend
is quite small -- well under 100 lines in this case. I'd hope to see a
lot more of these, and it seems unreasonable to make an armada of tiny
extensions instead of letting them live happily in their own directory
out of everyone's way.
Modified paths:
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/ExternalUser.php (added) (history)
  • /trunk/phase3/includes/User.php (modified) (history)
  • /trunk/phase3/includes/extauth (added) (history)
  • /trunk/phase3/includes/extauth/vB.php (added) (history)
  • /trunk/phase3/includes/specials/SpecialUserlogin.php (modified) (history)
  • /trunk/phase3/maintenance/archives/patch-external_user.sql (added) (history)
  • /trunk/phase3/maintenance/tables.sql (modified) (history)
  • /trunk/phase3/maintenance/updaters.inc (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/archives/patch-external_user.sql
@@ -0,0 +1,9 @@
 2+CREATE TABLE /*_*/external_user (
 3+ -- Foreign key to user_id
 4+ eu_wiki_id int unsigned NOT NULL PRIMARY KEY,
 5+
 6+ -- Some opaque identifier provided by the external database
 7+ eu_external_id varchar(255) binary NOT NULL
 8+) /*$wgDBTableOptions*/;
 9+
 10+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
Property changes on: trunk/phase3/maintenance/archives/patch-external_user.sql
___________________________________________________________________
Added: svn:eol-style
111 + native
Index: trunk/phase3/maintenance/updaters.inc
@@ -162,6 +162,7 @@
163163 array( 'do_log_search_population' ),
164164 array( 'add_field', 'logging', 'log_user_text', 'patch-log_user_text.sql' ),
165165 array( 'add_table', 'l10n_cache', 'patch-l10n_cache.sql' ),
 166+ array( 'add_table', 'external_user', 'patch-external_user.sql' ),
166167 ),
167168
168169 'sqlite' => array(
Index: trunk/phase3/maintenance/tables.sql
@@ -575,6 +575,20 @@
576576 CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60));
577577
578578
 579+--
 580+-- Track external user accounts, if ExternalAuth is used
 581+--
 582+CREATE TABLE /*_*/external_user (
 583+ -- Foreign key to user_id
 584+ eu_wiki_id int unsigned NOT NULL PRIMARY KEY,
 585+
 586+ -- Some opaque identifier provided by the external database
 587+ eu_external_id varchar(255) binary NOT NULL
 588+) /*$wgDBTableOptions*/;
 589+
 590+CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id);
 591+
 592+
579593 --
580594 -- Track interlanguage links
581595 --
Index: trunk/phase3/includes/User.php
@@ -3569,6 +3569,10 @@
35703570 }
35713571
35723572 protected function saveOptions() {
 3573+ global $wgAllowPrefChange;
 3574+
 3575+ $extuser = ExternalUser::newFromUser( $this );
 3576+
35733577 $this->loadOptions();
35743578 $dbw = wfGetDB( DB_MASTER );
35753579
@@ -3582,7 +3586,8 @@
35833587 return;
35843588
35853589 foreach( $saveOptions as $key => $value ) {
3586 - if ( ( is_null(self::getDefaultOption($key)) &&
 3590+ # Don't bother storing default values
 3591+ if ( ( is_null( self::getDefaultOption( $key ) ) &&
35873592 !( $value === false || is_null($value) ) ) ||
35883593 $value != self::getDefaultOption( $key ) ) {
35893594 $insert_rows[] = array(
@@ -3591,6 +3596,14 @@
35923597 'up_value' => $value,
35933598 );
35943599 }
 3600+ if ( $extuser && isset( $wgAllowPrefChange[$key] ) ) {
 3601+ switch ( $wgAllowPrefChange[$key] ) {
 3602+ case 'local': case 'message':
 3603+ break;
 3604+ case 'semiglobal': case 'global':
 3605+ $extuser->setPref( $key, $value );
 3606+ }
 3607+ }
35953608 }
35963609
35973610 $dbw->begin();
Index: trunk/phase3/includes/ExternalUser.php
@@ -0,0 +1,288 @@
 2+<?php
 3+
 4+# Copyright (C) 2009 Aryeh Gregor
 5+#
 6+# This program is free software; you can redistribute it and/or modify
 7+# it under the terms of the GNU General Public License as published by
 8+# the Free Software Foundation; either version 2 of the License, or
 9+# (at your option) any later version.
 10+#
 11+# This program is distributed in the hope that it will be useful,
 12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14+# GNU General Public License for more details.
 15+#
 16+# You should have received a copy of the GNU General Public License along
 17+# with this program; if not, write to the Free Software Foundation, Inc.,
 18+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 19+# http://www.gnu.org/copyleft/gpl.html
 20+
 21+/**
 22+ * A class intended to supplement, and perhaps eventually replace, AuthPlugin.
 23+ * See: http://www.mediawiki.org/wiki/ExternalAuth
 24+ *
 25+ * The class represents a user whose data is in a foreign database. The
 26+ * database may have entirely different conventions from MediaWiki, but it's
 27+ * assumed to at least support the concept of a user id (possibly not an
 28+ * integer), a user name (possibly not meeting MediaWiki's username
 29+ * requirements), and a password.
 30+ */
 31+abstract class ExternalUser {
 32+ protected function __construct() {}
 33+
 34+ /**
 35+ * Wrappers around initFrom*().
 36+ */
 37+
 38+ /**
 39+ * @param $name string
 40+ * @return mixed ExternalUser, or false on failure
 41+ */
 42+ public static function newFromName( $name ) {
 43+ global $wgExternalAuthType;
 44+ if ( is_null( $wgExternalAuthType ) ) {
 45+ return false;
 46+ }
 47+ $class = "ExternalUser_$wgExternalAuthType";
 48+ $obj = new $class;
 49+ if ( !$obj->initFromName( $name ) ) {
 50+ return false;
 51+ }
 52+ return $obj;
 53+ }
 54+
 55+ /**
 56+ * @param $id string
 57+ * @return mixed ExternalUser, or false on failure
 58+ */
 59+ public static function newFromId( $id ) {
 60+ global $wgExternalAuthType;
 61+ if ( is_null( $wgExternalAuthType ) ) {
 62+ return false;
 63+ }
 64+ $class = "ExternalUser_$wgExternalAuthType";
 65+ $obj = new $class;
 66+ if ( !$obj->initFromId( $id ) ) {
 67+ return false;
 68+ }
 69+ return $obj;
 70+ }
 71+
 72+ /**
 73+ * @param $cookie string
 74+ * @return mixed ExternalUser, or false on failure
 75+ */
 76+ public static function newFromCookie( $cookie ) {
 77+ global $wgExternalAuthType;
 78+ if ( is_null( $wgExternalAuthType ) ) {
 79+ return false;
 80+ }
 81+ $class = "ExternalUser_$wgExternalAuthType";
 82+ $obj = new $class;
 83+ if ( !$obj->initFromCookie( $cookie ) ) {
 84+ return false;
 85+ }
 86+ return $obj;
 87+ }
 88+
 89+ /**
 90+ * Creates the object corresponding to the given User object, assuming the
 91+ * user exists on the wiki and is linked to an external account. If either
 92+ * of these is false, this will return false.
 93+ *
 94+ * This is a wrapper around newFromId().
 95+ *
 96+ * @param $user User
 97+ * @return mixed ExternalUser or false
 98+ */
 99+ public static function newFromUser( $user ) {
 100+ global $wgExternalAuthType;
 101+ if ( is_null( $wgExternalAuthType ) ) {
 102+ # Short-circuit to avoid database query in common case so no one
 103+ # kills me
 104+ return false;
 105+ }
 106+
 107+ $dbr = wfGetDB( DB_SLAVE );
 108+ $id = $dbr->selectField( 'external_user', 'eu_external_id',
 109+ array( 'eu_wiki_id' => $user->getId() ), __METHOD__ );
 110+ if ( $id === false ) {
 111+ return false;
 112+ }
 113+ return self::newFromId( $id );
 114+ }
 115+
 116+ /**
 117+ * Given a name, which is a string exactly as input by the user in the
 118+ * login form but with whitespace stripped, initialize this object to be
 119+ * the corresponding ExternalUser. Return true if successful, otherwise
 120+ * false.
 121+ *
 122+ * @param $name string
 123+ * @return bool Success?
 124+ */
 125+ protected abstract function initFromName( $name );
 126+
 127+ /**
 128+ * Given an id, which was at some previous point in history returned by
 129+ * getId(), initialize this object to be the corresponding ExternalUser.
 130+ * Return true if successful, false otherwise.
 131+ *
 132+ * @param $id string
 133+ * @return bool Success?
 134+ */
 135+ protected abstract function initFromId( $id );
 136+
 137+ /**
 138+ * Given the user's cookie, initialize this object to the correct user if
 139+ * the cookie indicates that the user is logged into the external database.
 140+ * If successful, return true. If the external database doesn't support
 141+ * cookie-based authentication, or if the cookies don't belong to a
 142+ * logged-in user, return false.
 143+ *
 144+ * TODO: Actually use this.
 145+ *
 146+ * @param $cookie string
 147+ * @return bool Success?
 148+ */
 149+ protected function initFromCookie( $cookie ) {
 150+ return false;
 151+ }
 152+
 153+ /**
 154+ * This must return some identifier that stably, uniquely identifies the
 155+ * user. In a typical web application, this could be an integer
 156+ * representing the "user id". In other cases, it might be a string. In
 157+ * any event, the return value should be a string between 1 and 255
 158+ * characters in length; must uniquely identify the user in the foreign
 159+ * database; and, if at all possible, should be permanent.
 160+ *
 161+ * This will only ever be used to reconstruct this ExternalUser object via
 162+ * newFromId(). The resulting object in that case should correspond to the
 163+ * same user, even if details have changed in the interim (e.g., renames or
 164+ * preference changes).
 165+ *
 166+ * @return string
 167+ */
 168+ abstract public function getId();
 169+
 170+ /**
 171+ * This must return the name that the user would normally use for login to
 172+ * the external database. It is subject to no particular restrictions
 173+ * beyond rudimentary sanity, and in particular may be invalid as a
 174+ * MediaWiki username. It's used to auto-generate an account name that
 175+ * *is* valid for MediaWiki, either with or without user input, but
 176+ * basically is only a hint.
 177+ *
 178+ * @return string
 179+ */
 180+ abstract public function getName();
 181+
 182+ /**
 183+ * Is the given password valid for the external user? The password is
 184+ * provided in plaintext, with whitespace stripped but not otherwise
 185+ * modified.
 186+ *
 187+ * @param $password string
 188+ * @return bool
 189+ */
 190+ abstract public function authenticate( $password );
 191+
 192+ /**
 193+ * Retrieve the value corresponding to the given preference key. The most
 194+ * important values are:
 195+ *
 196+ * - emailaddress
 197+ * - language
 198+ *
 199+ * The value must meet MediaWiki's requirements for values of this type,
 200+ * and will be checked for validity before use. If the preference makes no
 201+ * sense for the backend, or it makes sense but is unset for this user, or
 202+ * is unrecognized, return null.
 203+ *
 204+ * $pref will never equal 'password', since passwords are usually hashed
 205+ * and cannot be directly retrieved. authenticate() is used for this
 206+ * instead.
 207+ *
 208+ * TODO: Currently this is only called for 'emailaddress'; generalize! Add
 209+ * some config option to decide which values are grabbed on user
 210+ * initialization.
 211+ *
 212+ * @param $pref string
 213+ * @return mixed
 214+ */
 215+ public function getPref( $pref ) {
 216+ return null;
 217+ }
 218+
 219+ /**
 220+ * Return an array of identifiers for all the foreign groups that this user
 221+ * has. The identifiers are opaque objects that only need to be
 222+ * specifiable by the administrator in LocalSettings.php when configuring
 223+ * $wgAutopromote. They may be, for instance, strings or integers.
 224+ *
 225+ * TODO: Support this in $wgAutopromote.
 226+ *
 227+ * @return array
 228+ */
 229+ public function getGroups() {
 230+ return array();
 231+ }
 232+
 233+ /**
 234+ * Given a preference key (e.g., 'emailaddress'), provide an HTML message
 235+ * telling the user how to change it in the external database. The
 236+ * administrator has specified that this preference cannot be changed on
 237+ * the wiki, and may only be changed in the foreign database. If no
 238+ * message is available, such as for an unrecognized preference, return
 239+ * false.
 240+ *
 241+ * TODO: Use this somewhere.
 242+ *
 243+ * @param $pref string
 244+ * @return mixed String or false
 245+ */
 246+ public static function prefMessage( $pref ) {
 247+ return false;
 248+ }
 249+
 250+ /**
 251+ * Set the given preference key to the given value. Two important
 252+ * preference keys that you might want to implement are 'password' and
 253+ * 'emailaddress'. If the set fails, such as because the preference is
 254+ * unrecognized or because the external database can't be changed right
 255+ * now, return false. If it succeeds, return true.
 256+ *
 257+ * If applicable, you should make sure to validate the new value against
 258+ * any constraints the external database may have, since MediaWiki may have
 259+ * more limited constraints (e.g., on password strength).
 260+ *
 261+ * TODO: Untested.
 262+ *
 263+ * @param $key string
 264+ * @param $value string
 265+ * @return bool Success?
 266+ */
 267+ public static function setPref( $key, $value ) {
 268+ return false;
 269+ }
 270+
 271+ /**
 272+ * Create a link for future reference between this object and the provided
 273+ * user_id. If the user was already linked, the old link will be
 274+ * overwritten.
 275+ *
 276+ * This is part of the core code and is not overridable by specific
 277+ * plugins. It's in this class only for convenience.
 278+ *
 279+ * @param $id int user_id
 280+ */
 281+ public final function link( $id ) {
 282+ $dbw = wfGetDB( DB_MASTER );
 283+ $dbw->replace( 'external_user',
 284+ array( 'eu_wiki_id', 'eu_external_id' ),
 285+ array( 'eu_wiki_id' => $id,
 286+ 'eu_external_id' => $this->getId() ),
 287+ __METHOD__ );
 288+ }
 289+}
Property changes on: trunk/phase3/includes/ExternalUser.php
___________________________________________________________________
Added: svn:eol-style
1290 + native
Index: trunk/phase3/includes/AutoLoader.php
@@ -74,6 +74,8 @@
7575 'ExternalStoreDB' => 'includes/ExternalStoreDB.php',
7676 'ExternalStoreHttp' => 'includes/ExternalStoreHttp.php',
7777 'ExternalStore' => 'includes/ExternalStore.php',
 78+ 'ExternalUser' => 'includes/ExternalUser.php',
 79+ 'ExternalUser_vB' => 'includes/extauth/vB.php',
7880 'FatalError' => 'includes/Exception.php',
7981 'FakeTitle' => 'includes/FakeTitle.php',
8082 'FauxRequest' => 'includes/WebRequest.php',
Index: trunk/phase3/includes/DefaultSettings.php
@@ -4028,3 +4028,60 @@
40294029 * );
40304030 */
40314031 $wgPoolCounterConf = null;
 4032+
 4033+/**
 4034+ * Use some particular type of external authentication. The specific
 4035+ * authentication module you use will normally require some extra settings to
 4036+ * be specified.
 4037+ *
 4038+ * null indicates no external authentication is to be used. Otherwise,
 4039+ * "ExternalUser_$wgExternalAuthType" must be the name of a non-abstract class
 4040+ * that extends ExternalUser.
 4041+ *
 4042+ * Core authentication modules can be found in includes/extauth/.
 4043+ */
 4044+$wgExternalAuthType = null;
 4045+
 4046+/**
 4047+ * Configuration for the external authentication. This may include arbitrary
 4048+ * keys that depend on the authentication mechanism. For instance,
 4049+ * authentication against another web app might require that the database login
 4050+ * info be provided. Check the file where your auth mechanism is defined for
 4051+ * info on what to put here.
 4052+ */
 4053+$wgExternalAuthConfig = array();
 4054+
 4055+/**
 4056+ * When should we automatically create local accounts when external accounts
 4057+ * already exist, if using ExternalAuth? Can have three values: 'never',
 4058+ * 'login', 'view'. 'view' requires the external database to support cookies,
 4059+ * and implies 'login'.
 4060+ *
 4061+ * TODO: Implement 'view' (currently behaves like 'login').
 4062+ */
 4063+$wgAutocreatePolicy = 'login';
 4064+
 4065+/**
 4066+ * Policies for how each preference is allowed to be changed, in the presence
 4067+ * of external authentication. The keys are preference keys, e.g., 'password'
 4068+ * or 'emailaddress' (see Preferences.php et al.). The value can be one of the
 4069+ * following:
 4070+ *
 4071+ * - local: Allow changes to this pref through the wiki interface but only
 4072+ * apply them locally (default).
 4073+ * - semiglobal: Allow changes through the wiki interface and try to apply them
 4074+ * to the foreign database, but continue on anyway if that fails.
 4075+ * - global: Allow changes through the wiki interface, but only let them go
 4076+ * through if they successfully update the foreign database.
 4077+ * - message: Allow no local changes for linked accounts; replace the change
 4078+ * form with a message provided by the auth plugin, telling the user how to
 4079+ * change the setting externally (maybe providing a link, etc.). If the auth
 4080+ * plugin provides no message for this preference, hide it entirely.
 4081+ *
 4082+ * Accounts that are not linked to an external account are never affected by
 4083+ * this setting. You may want to look at $wgHiddenPrefs instead.
 4084+ * $wgHiddenPrefs supersedes this option.
 4085+ *
 4086+ * TODO: Implement message, global.
 4087+ */
 4088+$wgAllowPrefChange = array();
Index: trunk/phase3/includes/specials/SpecialUserlogin.php
@@ -40,6 +40,8 @@
4141 var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
4242 var $mSkipCookieCheck, $mReturnToQuery;
4343
 44+ private $mExtUser = null;
 45+
4446 /**
4547 * Constructor
4648 * @param WebRequest $request A WebRequest object passed by reference
@@ -363,6 +365,14 @@
364366
365367 $wgAuth->initUser( $u, $autocreate );
366368
 369+ if ( $this->mExtUser ) {
 370+ $this->mExtUser->link( $u->getId() );
 371+ $email = $this->mExtUser->getPref( 'emailaddress' );
 372+ if ( $email && !$this->mEmail ) {
 373+ $u->setEmail( $email );
 374+ }
 375+ }
 376+
367377 $u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
368378 $u->saveSettings();
369379
@@ -417,6 +427,11 @@
418428 wfDebug( __METHOD__.": already logged in as {$this->mName}\n" );
419429 return self::SUCCESS;
420430 }
 431+
 432+ $this->mExtUser = ExternalUser::newFromName( $this->mName );
 433+
 434+ # TODO: Allow some magic here for invalid external names, e.g., let the
 435+ # user choose a different wiki name.
421436 $u = User::newFromName( $this->mName );
422437 if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) {
423438 return self::ILLEGAL;
@@ -496,27 +511,41 @@
497512 * @return integer Status code
498513 */
499514 function attemptAutoCreate( $user ) {
500 - global $wgAuth, $wgUser;
 515+ global $wgAuth, $wgUser, $wgAutocreatePolicy;
 516+
 517+ if ( $wgUser->isBlockedFromCreateAccount() ) {
 518+ wfDebug( __METHOD__.": user is blocked from account creation\n" );
 519+ return self::CREATE_BLOCKED;
 520+ }
 521+
501522 /**
502523 * If the external authentication plugin allows it, automatically cre-
503524 * ate a new account for users that are externally defined but have not
504525 * yet logged in.
505526 */
506 - if ( !$wgAuth->autoCreate() ) {
507 - return self::NOT_EXISTS;
 527+ if ( $this->mExtUser ) {
 528+ # mExtUser is neither null nor false, so use the new ExternalAuth
 529+ # system.
 530+ if ( $wgAutocreatePolicy == 'never' ) {
 531+ return self::NOT_EXISTS;
 532+ }
 533+ if ( !$this->mExtUser->authenticate( $this->mPassword ) ) {
 534+ return self::WRONG_PLUGIN_PASS;
 535+ }
 536+ } else {
 537+ # Old AuthPlugin.
 538+ if ( !$wgAuth->autoCreate() ) {
 539+ return self::NOT_EXISTS;
 540+ }
 541+ if ( !$wgAuth->userExists( $user->getName() ) ) {
 542+ wfDebug( __METHOD__.": user does not exist\n" );
 543+ return self::NOT_EXISTS;
 544+ }
 545+ if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
 546+ wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" );
 547+ return self::WRONG_PLUGIN_PASS;
 548+ }
508549 }
509 - if ( !$wgAuth->userExists( $user->getName() ) ) {
510 - wfDebug( __METHOD__.": user does not exist\n" );
511 - return self::NOT_EXISTS;
512 - }
513 - if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
514 - wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" );
515 - return self::WRONG_PLUGIN_PASS;
516 - }
517 - if ( $wgUser->isBlockedFromCreateAccount() ) {
518 - wfDebug( __METHOD__.": user is blocked from account creation\n" );
519 - return self::CREATE_BLOCKED;
520 - }
521550
522551 wfDebug( __METHOD__.": creating account\n" );
523552 $user = $this->initUser( $user, true );
@@ -526,8 +555,7 @@
527556 function processLogin() {
528557 global $wgUser, $wgAuth;
529558
530 - switch ($this->authenticateUserData())
531 - {
 559+ switch ( $this->authenticateUserData() ) {
532560 case self::SUCCESS:
533561 # We've verified now, update the real record
534562 if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) {
Index: trunk/phase3/includes/extauth/vB.php
@@ -0,0 +1,97 @@
 2+<?php
 3+
 4+# Copyright (C) 2009 Aryeh Gregor
 5+#
 6+# This program is free software; you can redistribute it and/or modify
 7+# it under the terms of the GNU General Public License as published by
 8+# the Free Software Foundation; either version 2 of the License, or
 9+# (at your option) any later version.
 10+#
 11+# This program is distributed in the hope that it will be useful,
 12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
 13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14+# GNU General Public License for more details.
 15+#
 16+# You should have received a copy of the GNU General Public License along
 17+# with this program; if not, write to the Free Software Foundation, Inc.,
 18+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 19+# http://www.gnu.org/copyleft/gpl.html
 20+
 21+/**
 22+ * This class supports the proprietary vBulletin forum system
 23+ * <http://www.vbulletin.com>, versions 3.5 and up. It calls no functions or
 24+ * code, only reads from the database. Example lines to put in
 25+ * LocalSettings.php:
 26+ *
 27+ * $wgExternalAuthType = 'vB';
 28+ * $wgExternalAuthConf = array(
 29+ * 'server' => 'localhost',
 30+ * 'username' => 'forum',
 31+ * 'password' => 'udE,jSqDJ<""p=fI.K9',
 32+ * 'dbname' => 'forum',
 33+ * 'tableprefix' => ''
 34+ * );
 35+ */
 36+class ExternalUser_vB extends ExternalUser {
 37+ private $mDb, $mRow;
 38+
 39+ protected function initFromName( $name ) {
 40+ return $this->initFromCond( array( 'username' => $name ) );
 41+ }
 42+
 43+ protected function initFromId( $id ) {
 44+ return $this->initFromCond( array( 'userid' => $id ) );
 45+ }
 46+
 47+ # initFromCookie() not yet implemented
 48+
 49+ private function initFromCond( $cond ) {
 50+ global $wgExternalAuthConf;
 51+
 52+ $this->mDb = new Database(
 53+ $wgExternalAuthConf['server'],
 54+ $wgExternalAuthConf['username'],
 55+ $wgExternalAuthConf['password'],
 56+ $wgExternalAuthConf['dbname'],
 57+ false, 0,
 58+ $wgExternalAuthConf['tableprefix']
 59+ );
 60+
 61+ $row = $this->mDb->selectRow(
 62+ 'user',
 63+ array( 'userid', 'username', 'password', 'salt', 'email', 'usergroupid',
 64+ 'membergroupids' ),
 65+ $cond,
 66+ __METHOD__
 67+ );
 68+ if ( !$row ) {
 69+ return false;
 70+ }
 71+ $this->mRow = $row;
 72+
 73+ return true;
 74+ }
 75+
 76+ public function getId() { return $this->mRow->userid; }
 77+ public function getName() { return $this->mRow->username; }
 78+
 79+ public function authenticate( $password ) {
 80+ return $this->mRow->password == md5( md5( $password )
 81+ . $this->mRow->salt );
 82+ }
 83+
 84+ public function getPref( $pref ) {
 85+ if ( $pref == 'emailaddress' && $this->mRow->email ) {
 86+ # TODO: only return if validated?
 87+ return $this->mRow->email;
 88+ }
 89+ return null;
 90+ }
 91+
 92+ public function getGroups() {
 93+ $groups = array( $this->mRow->usergroupid );
 94+ $groups = array_merge( $groups, explode( ',', $this->mRow->membergroupids ) );
 95+ $groups = array_unique( $groups );
 96+ return $groups;
 97+ }
 98+}
Property changes on: trunk/phase3/includes/extauth/vB.php
___________________________________________________________________
Added: svn:eol-style
199 + native
Index: trunk/phase3/RELEASE-NOTES
@@ -150,6 +150,7 @@
151151 thumbnails to be stored in a separate location to the source images.
152152 * If config/ directory is not executable, the command to make it executable
153153 now asks the user to cd to the correct directory
 154+* Add experimental new external authentication framework, ExternalAuth.
154155
155156 === Bug fixes in 1.16 ===
156157

Follow-up revisions

RevisionCommit summaryAuthorDate
r69912Follow up r53497. $wgExternalAuthConfig -> $wgExternalAuthConf...platonides21:11, 25 July 2010

Comments

#Comment by Brion VIBBER (talk | contribs)   00:19, 22 August 2009

eu_wiki_id is rather confusing; term 'wiki id' refers to the prefix+dbname for cross-wiki referencing in a farm setup. Haven't looked at the rest.

#Comment by Simetrical (talk | contribs)   01:47, 24 August 2009

How about eu_wiki_user and eu_external_user?

#Comment by Simetrical (talk | contribs)   20:31, 24 August 2009

. . . well, I'll wait for further feedback before committing changes, I guess.

#Comment by Werdna (talk | contribs)   18:31, 27 August 2009

Checked for anything that affects our deployment on Wikimedia and found nothing, so marking as OK with TODOs

#Comment by Tim Starling (talk | contribs)   05:03, 3 December 2009

Please rename:

  • prefMessage() to getPrefMessage(), needs verb
  • eu_wiki_id to eu_local_id, unclear
  • link() to linkToLocal() or some such longer name, link means other things in MediaWiki so it needs the longer name to avoid ambiguity.

Please change the schema and the configuration to allow for multiple external user providers, like we do for images with $wgForeignFileRepos. In my opinion, the fact that AuthPlugin couldn't do multiple user databases was its biggest architectural error. It's an error which is easy to avoid, so I'd hate to see it repeated in a rewrite. To support the feature would only need 100 lines or so of glue code, similar to RepoGroup.php.

This might be good enough as it is for Wikimedia deployment, but it's not good enough for a release. As soon as an interface is released, we're stuck with it forever. If you don't feel like working on it, it can be moved out to a branch for now.

#Comment by Simetrical (talk | contribs)   17:57, 3 December 2009

I'll try to find the time to fix this in the next week or two.

#Comment by Simetrical (talk | contribs)   19:51, 8 December 2009

I've done all the renames you've asked for. I haven't allowed multiple auth providers yet because I don't have any clear concept of who would use it, so I'm not sure how to design it. When you try to log in, should the software stop at the first external auth provider that returns true, or try to link the user to multiple accounts? If the user changes preferences, should it bubble up to everywhere the user is linked to? If the user isn't allowed to change local preferences when changing them externally fails ('global' in $wgAllowPrefChange), what if one provider fails but another doesn't? Should users be allowed to be linked to multiple auth sources at all, or is this unnecessary?

I don't think architectural changes would be needed in any event. The core part of the mechanism is very open-ended. If someone wants multiple auth mechanisms, I think the best way to do it would be to just write ExternalUser_Multiple. If it then turns out there's some other way of doing multiple auth sources that someone else wants, you could do some new class without everyone having to work around an awkward configuration format.

#Comment by Tim Starling (talk | contribs)   01:25, 9 December 2009

Thanks for the changes. Try to grab me on IRC when you're next online so we can talk about the architecture.

Status & tagging log