Extension:VBulletin/Users Integration

Description
The purpose of this page is to outline steps to be taken in order to integrate vBulletin (vB) users with MediaWiki (MW). I have a modest forum with 3,000 registered users. We decided to restrict those who can edit pages to registered users. We need to provide ONE LOGIN. That login needs to be the login they use with vB! We have built a similar 'hack/extension/mod' with OSCommerce (OSC). If they go to the 'store' and get to a point where they need to be logged in, they are automatically redirected to vB and logged in or are prompted to register there and THEN returned to the store. In this scenario, they are, for the sake of simplicity, automatically added as a new user with OSC.

The following is a start at outlining functions and behavior that would be affected where vB would manage logins to MW. We are using MediaWiki: 1.5beta3 (CVS 07.07.2005 21:20 UTC) special version EnotifWiki v3.35 at the moment.

Special Note to MW and vB developers: There are thousands of vB based sites and I would venture to say that increasing numbers are looking at how to deploy a wiki on their site. They are not likely to abandon their investment with vB. Please share your insights and perspectives regarding how vB sites can leverage MW. Granted, this would not be an issue where a MW site allows anonymous editing. There ARE advantages to encouraging users to register with MW. Wouldn't it be great if we can leverage the registered users of vB?!?!?!?

I am posting this without many details. I will throwing what I learn as I embark on this effort. I will also post a message in this vb hacks forum thread for anyone interested in this effort.

Working Example
I have taken Kai Backman's MediaWiki authentication plugin idea, fixed coding errors, and expanded greatly on it.

Working on: vBulletin 3.5.4 and MediaWiki 1.6.3

Features:
 * Allows you to run MediaWiki with your vBulletin user database
 * Disallows users with invalid username characters
 * Disallows users who are not part of specified usergroups
 * Sets users to sysop status in MediaWiki if they are part of a specified admin usergroup
 * Removes users from sysop status in MediaWiki if they no longer are a part of a specified admin usergroup
 * For same-database setups, allows easy installation

Possible Future Features
''These are possible features for inclusion in the how-to in the future. They have not been investigated for their feasibility, but are here to let you know they have been requested and I am thinking about them. If you know how to add the features, please let us know.''
 * No-login required (automatic) integration
 * Option: User profile field points to, or has option to link to vBulletin profile
 * To register, link the user to vBulletin's registration script on the login pages

How To
require_once("AuthPlugin_vBulletin.php"); // if vBulletin and MediaWiki are not installed on the same database // change these values to reflect your vBulletin database information $wgAuth = new AuthPlugin_vBulletin($wgDBserver, $wgDBuser, $wgDBpassword, $wgDBname, "vb_"); If you are running vBulletin and MediaWiki in the same database, then it already uses the connection information. If you are not running vBulletin and MediaWiki in the same database, please change the strings to reflect your vBulletin database information. In either case, the last value is whatever your vBulletin table prefix is. $wgWhitelistAccount = array ( "sysop" => 1, "developer" => 1 ); $wgGroupPermissions['*']['createaccount'] = false; $wgGroupPermissions['*']['read'] = false; $wgGroupPermissions['*']['edit'] = false; $wgWhitelistRead = array ("Special:Userlogin"); This prevents people from registering new accounts on the wiki, requiring people to register on vBulletin. It also prevents anonymous edits. This code may only work on MediaWiki 1.5.x and above, but I am unsure. <?php /** * Authentication plugin interface. Instantiate a subclass of AuthPlugin * and set $wgAuth to it to authenticate against some external tool. * * The default behavior is not to do anything, and use the local user * database for all authentication. A subclass can require that all * accounts authenticate externally, or use it only as a fallback; also * you can transparently create internal wiki accounts the first time * someone logs in who can be authenticated externally. * * This interface is new, and might change a bit before 1.4.0 final is * done... * * AuthPlugin extension by Daniel Gravenor c/o HolisticEarth.org * AuthPlugin original by Kai Backman * * @package MediaWiki */ require_once("includes/AuthPlugin.php");
 * Install Reynaldovb's Restrict usernames to alphanumeric and underscore plugin, and disallow both spaces and underscores
 * Optional: On boards that already have existing users, somehow have their usernames changed so that they are only alphanumeric. This is required if you wish your users to be able to login and edit the wiki using their vBulletin username.  The reason you have to do this is because MediaWiki has some Restrictions on what can go in a page title, and as usernames have to be passed as page titles, they also have to adhere to the same restrictions.  If you do not change the usernames, I have included a check to not allow users to login if their username contains non-alphanumeric characters.
 * Open your wiki/LocalSettings.php file
 * Insert the following code at the end of the file, before the ?>:
 * 1) vBulletin integration script
 * Insert this code below the require_once( "includes/DefaultSettings.php" ); at the top of the LocalSettings.php file:
 * 1) Disabling new user registrations
 * 1) Disabling anonymous edits
 * Copy this AuthPlugin_vBulletin.php file and put it in your main wiki directory, ie: /wiki/AuthPlugin_vBulletin.php

class AuthPlugin_vBulletin extends AuthPlugin { // Create a persistent DB connection var $vb_database; function AuthPlugin_vBulletin($host, $username, $password, $dbname, $prefix) { $this->vb_database = mysql_pconnect($host, $username, $password); mysql_select_db($dbname, $this->vb_database); $this->vb_prefix = $prefix; // set the usergroups for those who can edit the wiki $this->allowed_usergroups = Array(2, 5, 6, 7); // set the usergroups for the administrators $this->admin_usergroups = Array(6, 9); $this->user_rights = Array("sysop"); // search pattern to only accept alphanumeric or underscore characters in usernames // if they have illegal characters, their name cannot exist, period $this->searchpattern = "/[^a-zA-Z0-9]+/"; }	/**	* Check whether there exists a user account with the given name. * The name will be normalized to MediaWiki's requirements, so	* you might need to munge it (for instance, for lowercase initial	* letters). *	* @param string $username * @return bool * @access public */	function userExists( $username ) { // if no illegal characters are found in their username, then check to see if they exist if (!preg_match($this->searchpattern, $username)) { $username = addslashes($username); $vb_find_user_query = "SELECT usergroupid FROM ". $this->vb_prefix. "user WHERE LOWER(username)=LOWER('" . $username . "')"; $vb_find_result = mysql_query($vb_find_user_query, $this->vb_database); // make sure that there is only one person with the username if (mysql_num_rows($vb_find_result) == 1) { $vb_userinfo = mysql_fetch_assoc($vb_find_result); mysql_free_result($vb_find_result); // Only registered and admins. Banned and unregistered don't belong here. if (in_array($vb_userinfo['usergroupid'], $this->allowed_usergroups)) { return TRUE; }			}		}		// if no one is registered with that username, or there are more than 1 entries // or they have illegal characters return FALSE (they do not exist) return FALSE; }	/**	* Check if a username+password pair is a valid login. * The name will be normalized to MediaWiki's requirements, so	* you might need to munge it (for instance, for lowercase initial	* letters). *	* @param string $username * @param string $password * @return bool * @access public */	function authenticate( $username, $password ) { // if their name does not contain any illegal characters, let them try to login if (!preg_match($this->searchpattern, $username)) { $username = addslashes($username); $vb_find_user_query = "SELECT password, salt, usergroupid FROM ". $this->vb_prefix. "user WHERE LOWER(username)=LOWER('" . $username . "')"; $vb_find_result = mysql_query($vb_find_user_query, $this->vb_database); if (mysql_num_rows($vb_find_result) == 1) { $vb_userinfo = mysql_fetch_assoc($vb_find_result); mysql_free_result($vb_find_result); // Only registered and admins. Banned and unregistered don't belong here. if (in_array($vb_userinfo['usergroupid'], $this->allowed_usergroups)) { if(md5(md5($password). $vb_userinfo['salt']) == $vb_userinfo['password']) return true; }			}		}		return false; }	/**	* When a user logs in, optionally fill in preferences and such. * For instance, you might pull the email address or real name from the * external user database. *	* The User object is passed by reference so it can be modified; don't	* forget the & on your function declaration. *	* @param User $user * @access public */	function updateUser( &$user ) { # Override this and do something $vb_find_user_query = "SELECT usergroupid, membergroupids FROM ". $this->vb_prefix. "user WHERE LOWER(username)=LOWER('" . addslashes($user->mName) . "')"; $vb_find_result = mysql_query($vb_find_user_query, $this->vb_database) or die("Could not find username"); if(mysql_num_rows($vb_find_result) == 1) { $vb_userinfo = mysql_fetch_assoc($vb_find_result); mysql_free_result($vb_find_result); // go through the users member groups to see if one of them is administrative $user_membergroups = explode(",", $vb_userinfo['membergroupids']); $admin_secondary = FALSE; for ($x = 0; $x < count($user_membergroups); $x++) { if (in_array($user_membergroups[$x], $this->admin_usergroups)) $admin_secondary = TRUE; }			if (in_array($vb_userinfo['usergroupid'], $this->admin_usergroups) || $admin_secondary === TRUE) { // if a user is not a sysop, make them a sysop if (!in_array("sysop", $user->getEffectiveGroups)) { $user->addGroup('sysop'); return TRUE; }			}			// if the user is not an administrator, but they were, and they are still a sysop, remove their sysop status if (!in_array($vb_userinfo['usergroupid'], $this->admin_usergroups) && $admin_secondary === FALSE) { if (in_array("sysop", $user->getEffectiveGroups)) { $user->removeGroup('sysop'); return TRUE; }			}		}		return FALSE; }

/**	* Return true if the wiki should create a new local account automatically * when asked to login a user who doesn't exist locally but does in the * external auth database. *	* If you don't automatically create accounts, you must still create * accounts in some way. It's not possible to authenticate without * a local account. *	* This is just a question, and shouldn't perform any actions. *	* @return bool * @access public */	function autoCreate { return true; }	/**	* Return true to prevent logins that don't authenticate here from being * checked against the local database's password fields. *	* This is just a question, and shouldn't perform any actions. *	* @return bool * @access public */	function strict { return true; }	/**	* When creating a user account, optionally fill in preferences and such. * For instance, you might pull the email address or real name from the * external user database. *	* The User object is passed by reference so it can be modified; don't	* forget the & on your function declaration. *	* @param User $user * @access public */	function initUser( &$user ) { $vb_find_user_query = "SELECT email, usergroupid FROM ". $this->vb_prefix. "user WHERE LOWER(username)=LOWER('" . addslashes($user->mName) . "')"; $vb_find_result = mysql_query($vb_find_user_query, $this->vb_database); if(mysql_num_rows($vb_find_result) == 1) { $vb_userinfo = mysql_fetch_assoc($vb_find_result); mysql_free_result($vb_find_result); $user->mEmail = $vb_userinfo['email']; $user->mEmailAuthenticated = wfTimestampNow; }	} } ?>

Anonymous / Guest Visitors
In vB, a nice feature is to track the non-registered visitors. You can see how many members AND GUESTS on presently at the site... even down to the thread level.

It occurs to me that whenever someone goes to MW, it triggers a vB session and/or logs them in if there is a cookie. The reason is that some, actually more and more, people are going directly to the MW. I would like to make sure that the site stats as to who has been to the domain are maintained site-wide.

Login

 * If attempt is made to perform a function that requires login, redirect to vB to login.
 * Afterward, return user to page from where he came from.
 * Files to modify

Registration

 * Redirect user to vB for registration.
 * Use the following details to create a MW user (with link back to vB)
 * vB:userid
 * vB:username
 * vB:timezoneoffset
 * vB:salt
 * email authentification. Handled through vB.
 * vB:usergroupid. Need to validate that user has had email authenticated. Decision to only allow validated users to update wiki pages.
 * vB:email? Maybe not because all email will be done using vB email address.
 * vB:field? Disable e-mail from other users. See (http://www.noblood.org/forum/profile.php?do=editoptions)
 * Real Name: If user has entered their real name, use that or else default to vB username
 * MW:Nickname = vB:username
 * Ignore MW password, use vB only
 * vB:membergroupids, this contains the groups that the user is a member of. We need to map these to MW:user_groups
 * vB:usergroup, this contains the NAME of the groups that will be mapped to the MW:user_groups
 * Files to modify

Passwords

 * Only use vB

Authentication Method
Here is some base code for authenticating. This is not a working example, it is only a starting point.

This code needs to be modified to accommodate MediaWiki's needs by setting cookies. The query also may need to be enhanced to to look for things MediaWiki uses in its cookies/sessions. Thanks to Alfarin over at vb.com for this start.

--Nogerorob 17:48, 28 July 2005 (UTC)

Preferences

 * Default switches ON for:
 * Send me an email on page changes
 * Send me an email when my user talk page is changed
 * Need to disable certain options (preferences) that are now handled by vB
 * User dialog: Real name, email, nickname, raw signature, language
 * Password dialog: disable all, Use vB instead
 * Email dialog: Disable e-mail from other users (Use vB switch for that)
 * Skin dialog: Disable all (default to MonoBook
 * Time zone dialog: Disable, use vB

Email User

 * Link in MW toolbox: Special:Emailuser/ to link to vB's function (vB/sendmessage.php?do=mailmember&u=#####) for this.
 * OR since email addresses are synchronized with MW, make no change.

Email Notifications

 * Use the email from MW since email addresses are synchronized.

Links back to vB

 * MW User: page to provide (in header, right-justified) a link automatically to vB user profile and link to email or PM.
 * In localsettings.php, provide for user defined icon path to define vB user profile link AND one for link to vB/MW email function
 * In preferences, user data dialog: Add link to vB user control panel (profile)

MW Special Pages

 * Special:Listusers: We need to add a link/icon next to each listed user to their vB profile.

Useful Extension Proposals

 * username=johndoe;fld=userTblFieldName Return vB user table field value
 * username=johndoe Return site defined icon that links to vB email user feature.

Skin/Style Issues

 * Question: Make MW the entry point with link to vB OR make vB the entry point to site?
 * If MW is the entry point..
 * Need to add key userCP type menu options
 * Need (IMHO) to present key vB type info on front page such as Who's Online!!!! Since MW caches pages, where/how to inject some real-time info into page? Turn off caching? Insert 'live' content AFTER footer? :(

vB Functions That Affect MW

 * User updates password
 * User changes email
 * User toggles switch to accept emails from others
 * User changes timezoneoffset
 * User changes real name fields
 * User changes homepage, or other fields

Sessions

 * Need to keep vB sessions alive so that we can see who's online whether they are in MW or in vB

Integration Issues
The largest problem with integrating vBulletin and MediaWiki is the use of characters not allowed in the MediaWiki namespace. In MediaWiki, the characters allowed in a username are the same that are allowed in a page name, and you can find those on the Help:Page_name page.

There are two easy possibilities, which I have not explored or found information on:
 * If you have a small vB membership, renaming people's usernames so that the do not have the unwanted characters. In this case you will also want to edit vBulletin's registration function to disallow unwanted characters as well.
 * Making any links to a user's page link to their user page on vBulletin. Theoretically, this would allow people to keep their usernames intact, while not interfering with the MediaWiki namespace.