I have a single sign-on script that will automatically create an account for new users visiting my site for the first time.
I found instances in my logs which appear to be race conditions where multiple accounts were created for the same user. The user accounts are identical (same username, email, etc) and only the userids are different.
The guts of my sso script are as follows:
PHP Code:
// search the VB db for an existing user with the same email
$userid = $vbulletin->db->query_first_slave("
SELECT userid, salt
FROM ".TABLE_PREFIX."user
WHERE LOWER(email) = LOWER('".mysql_real_escape_string($searchResult[0]['mail'][0])."')");
if ($userid)
{
if($enableLogging) writeLog($fpSSO, "user exists in vb");
$newuser->set_existing(fetch_userinfo($userid['userid'], 0));
// set profile fields
$newuser->set('username', $vbulletin->GPC['vb_login_username']);
$newuser->set('password', $randomPass);
$newuser->set('passworddate', 'FROM_UNIXTIME('.TIMENOW.')', false);
}
else
{
if($enableLogging) writeLog($fpSSO, "user not found in vb, creating new user");
$newuser->set('email', strtolower($searchResult[0]['mail'][0]));
$newuser->set('username', $vbulletin->GPC['vb_login_username']);
$newuser->set('usergroupid', 2);
$newuser->set_bitfield('options', 'adminemail', true);
$newuser->set_bitfield('options', 'emailonpm', true);
$newuser->set_bitfield('options', 'vm_enable', true);
$newuser->set('pmpopup', 1);
$newuser->set_userfields($userFields);
$newuser->set('password', $randomPass);
$newuser->set('passworddate', 'FROM_UNIXTIME('.TIMENOW.')', false);
}
$newuser->pre_save();
if ($newuser->errors)
{
if($enableLogging) writeLog($fpSSO, "Error creating/updating user: ".$newuser->errors[0]);
eval(standard_error("Error creating/updating user<br/>".$newuser->errors[0]));
process_logout();
return;
}
else
{
$newuserid = $newuser->save();
if($enableLogging) writeLog($fpSSO, "User account saved successfully: setting cookies and logging in...");
// when dealing with a new user, the previous query to get userid and salt returned nothing. rerun query to get new values.
if(!isset($userID))
{
$userIDq = $vbulletin->db->query_first_slave("
SELECT userid
FROM ".TABLE_PREFIX."user
WHERE LOWER(email) = LOWER('".mysql_real_escape_string($searchResult[0]['mail'][0])."')");
}
$vbulletin->userinfo = fetch_userinfo($userIDq['userid'], $option, $languageid); ;
// with spoofed data in place, perform authentication
$ssoAuth = verify_authentication($vbulletin->GPC['vb_login_username'], $randomPass, '', '', true, true);
if($enableLogging) writeLog($fpSSO, "verify_authentication returned " . $ssoAuth);
// create session
process_new_login('', 1, '');
// redirect
exec_header_redirect($_SERVER['REQUEST_URI']);
}
For a race condition to occur, the SQL query at the beginning would have to return no results. In that case, separate instances of this script would be racing to create an account for the same user. What are the ramifications of using query_first_slave? Should I be using query_first instead? Any ideas for preventing this problem?
I believe I know where the requests are coming from. We have a Terms of Service button that new users must click, and I have a feeling people are going crazy clicking the button. I am going to add some JavaScript to that button to disable it after it is clicked once. However, I would like to prevent the problem in my sso script as well!
Thanks!