Commit 713d9618 by Scott

Merge branch 'pr/353' into 1.8

Use password_hash functions.
parents 3fd0654f 1442ef15
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
exit; exit;
} }
define('QA_DB_VERSION_CURRENT', 61); define('QA_DB_VERSION_CURRENT', 62);
function qa_db_user_column_type_verify() function qa_db_user_column_type_verify()
...@@ -107,7 +107,8 @@ ...@@ -107,7 +107,8 @@
'avatarwidth' => 'SMALLINT UNSIGNED', // pixel width of stored avatar 'avatarwidth' => 'SMALLINT UNSIGNED', // pixel width of stored avatar
'avatarheight' => 'SMALLINT UNSIGNED', // pixel height of stored avatar 'avatarheight' => 'SMALLINT UNSIGNED', // pixel height of stored avatar
'passsalt' => 'BINARY(16)', // salt used to calculate passcheck - null if no password set for direct login 'passsalt' => 'BINARY(16)', // salt used to calculate passcheck - null if no password set for direct login
'passcheck' => 'BINARY(20)', // checksum from password and passsalt - null if no passowrd set for direct login 'passcheck' => 'BINARY(20)', // checksum from password and passsalt - null if no password set for direct login
'passhash' => 'VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL', // password_hash
'level' => 'TINYINT UNSIGNED NOT NULL', // basic, editor, admin, etc... 'level' => 'TINYINT UNSIGNED NOT NULL', // basic, editor, admin, etc...
'loggedin' => 'DATETIME NOT NULL', // time of last login 'loggedin' => 'DATETIME NOT NULL', // time of last login
'loginip' => 'INT UNSIGNED NOT NULL', // INET_ATON of IP address of last login 'loginip' => 'INT UNSIGNED NOT NULL', // INET_ATON of IP address of last login
...@@ -1430,13 +1431,13 @@ ...@@ -1430,13 +1431,13 @@
$keyrecalc['dorecalcpoints'] = true; $keyrecalc['dorecalcpoints'] = true;
break; break;
// Up to here: Verison 1.7 // Up to here: Version 1.7
case 59: case 59:
// upgrade from alpha version removed // upgrade from alpha version removed
break; break;
// Up to here: Verison 1.7.1 // Up to here: Version 1.7.1
case 60: case 60:
// add new category widget - note title must match that from qa_register_core_modules() // add new category widget - note title must match that from qa_register_core_modules()
...@@ -1459,8 +1460,13 @@ ...@@ -1459,8 +1460,13 @@
break; break;
// Up to here: Verison 1.8 alpha case 62:
// add column to qa_users to handle new bcrypt passwords
qa_db_upgrade_query('ALTER TABLE ^users ADD COLUMN passhash '.$definitions['users']['passhash'].' AFTER passcheck');
qa_db_upgrade_query($locktablesquery);
break;
// Up to here: Version 1.8 alpha
} }
qa_db_set_db_version($newversion); qa_db_set_db_version($newversion);
...@@ -1523,4 +1529,4 @@ ...@@ -1523,4 +1529,4 @@
/* /*
Omit PHP closing tag to help avoid accidental output Omit PHP closing tag to help avoid accidental output
*/ */
\ No newline at end of file
...@@ -1181,7 +1181,7 @@ ...@@ -1181,7 +1181,7 @@
{ {
return array( return array(
'columns' => array( 'columns' => array(
'^users.userid', 'passsalt', 'passcheck' => 'HEX(passcheck)', 'email', 'level', 'emailcode', 'handle', '^users.userid', 'passsalt', 'passcheck' => 'HEX(passcheck)', 'passhash', 'email', 'level', 'emailcode', 'handle',
'created' => 'UNIX_TIMESTAMP(created)', 'sessioncode', 'sessionsource', 'flags', 'loggedin' => 'UNIX_TIMESTAMP(loggedin)', 'created' => 'UNIX_TIMESTAMP(created)', 'sessioncode', 'sessionsource', 'flags', 'loggedin' => 'UNIX_TIMESTAMP(loggedin)',
'loginip' => 'INET_NTOA(loginip)', 'written' => 'UNIX_TIMESTAMP(written)', 'writeip' => 'INET_NTOA(writeip)', 'loginip' => 'INET_NTOA(loginip)', 'written' => 'UNIX_TIMESTAMP(written)', 'writeip' => 'INET_NTOA(writeip)',
'avatarblobid' => 'BINARY avatarblobid', // cast to BINARY due to MySQL bug which renders it signed in a union 'avatarblobid' => 'BINARY avatarblobid', // cast to BINARY due to MySQL bug which renders it signed in a union
......
...@@ -44,13 +44,22 @@ ...@@ -44,13 +44,22 @@
{ {
require_once QA_INCLUDE_DIR.'util/string.php'; require_once QA_INCLUDE_DIR.'util/string.php';
$salt=isset($password) ? qa_random_alphanum(16) : null; if (QA_PASSWORD_HASH) {
qa_db_query_sub(
'INSERT INTO ^users (created, createip, email, passhash, level, handle, loggedin, loginip) '.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, #, $, NOW(), COALESCE(INET_ATON($), 0))',
$ip, $email, isset($password) ? password_hash($password, PASSWORD_BCRYPT) : null, (int)$level, $handle, $ip
);
} else {
$salt = isset($password) ? qa_random_alphanum(16) : null;
qa_db_query_sub(
'INSERT INTO ^users (created, createip, email, passsalt, passcheck, level, handle, loggedin, loginip) '.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, UNHEX($), #, $, NOW(), COALESCE(INET_ATON($), 0))',
$ip, $email, $salt, isset($password) ? qa_db_calc_passcheck($password, $salt) : null, (int)$level, $handle, $ip
);
}
qa_db_query_sub(
'INSERT INTO ^users (created, createip, email, passsalt, passcheck, level, handle, loggedin, loginip) '.
'VALUES (NOW(), COALESCE(INET_ATON($), 0), $, $, UNHEX($), #, $, NOW(), COALESCE(INET_ATON($), 0))',
$ip, $email, $salt, isset($password) ? qa_db_calc_passcheck($password, $salt) : null, (int)$level, $handle, $ip
);
return qa_db_last_insert_id(); return qa_db_last_insert_id();
} }
...@@ -154,12 +163,19 @@ ...@@ -154,12 +163,19 @@
require_once QA_INCLUDE_DIR.'util/string.php'; require_once QA_INCLUDE_DIR.'util/string.php';
$salt=qa_random_alphanum(16); if (QA_PASSWORD_HASH) {
qa_db_query_sub(
'UPDATE ^users SET passhash=$, passsalt=NULL, passcheck=NULL WHERE userid=$',
password_hash($password, PASSWORD_BCRYPT), $userid
);
} else {
$salt = qa_random_alphanum(16);
qa_db_query_sub( qa_db_query_sub(
'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$', 'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$',
$salt, qa_db_calc_passcheck($password, $salt), $userid $salt, qa_db_calc_passcheck($password, $salt), $userid
); );
}
} }
...@@ -335,4 +351,4 @@ ...@@ -335,4 +351,4 @@
/* /*
Omit PHP closing tag to help avoid accidental output Omit PHP closing tag to help avoid accidental output
*/ */
\ No newline at end of file
...@@ -37,7 +37,7 @@ ...@@ -37,7 +37,7 @@
if (QA_FINAL_EXTERNAL_USERS) if (QA_FINAL_EXTERNAL_USERS)
qa_fatal_error('User accounts are handled by external code'); qa_fatal_error('User accounts are handled by external code');
$userid=qa_get_logged_in_userid(); $userid = qa_get_logged_in_userid();
if (!isset($userid)) if (!isset($userid))
qa_redirect('login'); qa_redirect('login');
...@@ -52,10 +52,16 @@ ...@@ -52,10 +52,16 @@
qa_db_userfields_selectspec() qa_db_userfields_selectspec()
); );
$changehandle=qa_opt('allow_change_usernames') || ((!$userpoints['qposts']) && (!$userpoints['aposts']) && (!$userpoints['cposts'])); $changehandle = qa_opt('allow_change_usernames') || (!$userpoints['qposts'] && !$userpoints['aposts'] && !$userpoints['cposts']);
$doconfirms=qa_opt('confirm_user_emails') && ($useraccount['level']<QA_USER_LEVEL_EXPERT); $doconfirms = qa_opt('confirm_user_emails') && $useraccount['level'] < QA_USER_LEVEL_EXPERT;
$isconfirmed=($useraccount['flags'] & QA_USER_FLAGS_EMAIL_CONFIRMED) ? true : false; $isconfirmed = ($useraccount['flags'] & QA_USER_FLAGS_EMAIL_CONFIRMED) ? true : false;
$haspassword=isset($useraccount['passsalt']) && isset($useraccount['passcheck']);
$haspasswordold = isset($useraccount['passsalt']) && isset($useraccount['passcheck']);
if (QA_PASSWORD_HASH) {
$haspassword = isset($useraccount['passhash']);
} else {
$haspassword = $haspasswordold;
}
$permit_error = qa_user_permit_error(); $permit_error = qa_user_permit_error();
$isblocked = $permit_error !== false; $isblocked = $permit_error !== false;
$pending_confirmation = $doconfirms && $permit_error == 'confirm'; $pending_confirmation = $doconfirms && $permit_error == 'confirm';
...@@ -204,9 +210,18 @@ ...@@ -204,9 +210,18 @@
else { else {
$errors = array(); $errors = array();
$legacyPassError = strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck']);
if ($haspassword && (strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck'])))
$errors['oldpassword'] = qa_lang('users/password_wrong'); if (QA_PASSWORD_HASH) {
$passError = !password_verify($inoldpassword,$useraccount['passhash']);
if (($haspasswordold && $legacyPassError) || (!$haspasswordold && $haspassword && $passError)) {
$errors['oldpassword'] = qa_lang('users/password_wrong');
}
} else {
if ($haspassword && $legacyPassError) {
$errors['oldpassword'] = qa_lang('users/password_wrong');
}
}
$useraccount['password'] = $inoldpassword; $useraccount['password'] = $inoldpassword;
$errors = $errors + qa_password_validate($innewpassword1, $useraccount); // array union $errors = $errors + qa_password_validate($innewpassword1, $useraccount); // array union
...@@ -463,7 +478,7 @@ ...@@ -463,7 +478,7 @@
), ),
); );
if (!$haspassword) { if (!$haspassword && !$haspasswordold) {
$qa_content['form_password']['fields']['old']['type']='static'; $qa_content['form_password']['fields']['old']['type']='static';
$qa_content['form_password']['fields']['old']['value']=qa_lang_html('users/password_none'); $qa_content['form_password']['fields']['old']['value']=qa_lang_html('users/password_none');
} }
......
...@@ -68,9 +68,30 @@ ...@@ -68,9 +68,30 @@
$inuserid=$matchusers[0]; $inuserid=$matchusers[0];
$userinfo=qa_db_select_with_pending(qa_db_user_account_selectspec($inuserid, true)); $userinfo=qa_db_select_with_pending(qa_db_user_account_selectspec($inuserid, true));
if (strtolower(qa_db_calc_passcheck($inpassword, $userinfo['passsalt'])) == strtolower($userinfo['passcheck'])) { // login and redirect $legacyPassOk = strtolower(qa_db_calc_passcheck($inpassword, $userinfo['passsalt'])) == strtolower($userinfo['passcheck']);
if (QA_PASSWORD_HASH) {
$haspassword = isset($userinfo['passhash']);
$haspasswordold = isset($userinfo['passsalt']) && isset($userinfo['passcheck']);
$passOk = password_verify($inpassword,$userinfo['passhash']);
if (($haspasswordold && $legacyPassOk) || ($haspassword && $passOk)) {
// upgrade password or rehash, when options like the cost parameter changed
if ($haspasswordold || password_needs_rehash($userinfo['passhash'], PASSWORD_BCRYPT)) {
qa_db_user_set_password($inuserid, $inpassword);
}
} else {
$errors['password']=qa_lang('users/password_wrong');
}
} else {
if (!$legacyPassOk) {
$errors['password']=qa_lang('users/password_wrong');
}
}
if (!isset($errors['password'])) {
// login and redirect
require_once QA_INCLUDE_DIR.'app/users.php'; require_once QA_INCLUDE_DIR.'app/users.php';
qa_set_logged_in_user($inuserid, $userinfo['handle'], !empty($inremember)); qa_set_logged_in_user($inuserid, $userinfo['handle'], !empty($inremember));
$topath=qa_get('to'); $topath=qa_get('to');
...@@ -81,9 +102,7 @@ ...@@ -81,9 +102,7 @@
qa_redirect('account'); qa_redirect('account');
else else
qa_redirect(''); qa_redirect('');
}
} else
$errors['password']=qa_lang('users/password_wrong');
} else } else
$errors['emailhandle']=qa_lang('users/user_not_found'); $errors['emailhandle']=qa_lang('users/user_not_found');
...@@ -174,4 +193,4 @@ ...@@ -174,4 +193,4 @@
/* /*
Omit PHP closing tag to help avoid accidental output Omit PHP closing tag to help avoid accidental output
*/ */
\ No newline at end of file
...@@ -49,25 +49,6 @@ ...@@ -49,25 +49,6 @@
qa_initialize_php(); qa_initialize_php();
qa_initialize_constants_1(); qa_initialize_constants_1();
/**
* JSON compatibility layer for PHP 5.1
*/
if (!function_exists('json_encode') && !function_exists('json_decode')) {
require_once QA_INCLUDE_DIR.'vendor/JSON.php';
function json_encode($json)
{
$service = new Services_JSON();
return $service->encode($json);
}
function json_decode($json, $assoc = false)
{
$service = new Services_JSON($assoc ? SERVICES_JSON_LOOSE_TYPE : 0);
return $service->decode($json);
}
}
if (defined('QA_WORDPRESS_LOAD_FILE')) // if relevant, load WordPress integration in global scope if (defined('QA_WORDPRESS_LOAD_FILE')) // if relevant, load WordPress integration in global scope
require_once QA_WORDPRESS_LOAD_FILE; require_once QA_WORDPRESS_LOAD_FILE;
...@@ -196,6 +177,31 @@ ...@@ -196,6 +177,31 @@
if (!is_readable(QA_WORDPRESS_LOAD_FILE)) if (!is_readable(QA_WORDPRESS_LOAD_FILE))
qa_fatal_error('Could not find wp-load.php file for WordPress integration - please check QA_WORDPRESS_INTEGRATE_PATH in qa-config.php'); qa_fatal_error('Could not find wp-load.php file for WordPress integration - please check QA_WORDPRESS_INTEGRATE_PATH in qa-config.php');
} }
// Polyfills
// JSON compatibility layer for PHP 5.1
if (!function_exists('json_encode') && !function_exists('json_decode')) {
require_once QA_INCLUDE_DIR.'vendor/JSON.php';
function json_encode($json)
{
$service = new Services_JSON();
return $service->encode($json);
}
function json_decode($json, $assoc = false)
{
$service = new Services_JSON($assoc ? SERVICES_JSON_LOOSE_TYPE : 0);
return $service->decode($json);
}
}
// password_hash compatibility for 5.3-5.4
define('QA_PASSWORD_HASH', !qa_php_version_below('5.3.7'));
if (QA_PASSWORD_HASH) {
require_once QA_INCLUDE_DIR.'vendor/password_compat.php';
}
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment