Commit 713d9618 by Scott

Merge branch 'pr/353' into 1.8

Use password_hash functions.
parents 3fd0654f 1442ef15
......@@ -25,7 +25,7 @@
exit;
}
define('QA_DB_VERSION_CURRENT', 61);
define('QA_DB_VERSION_CURRENT', 62);
function qa_db_user_column_type_verify()
......@@ -107,7 +107,8 @@
'avatarwidth' => 'SMALLINT UNSIGNED', // pixel width 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
'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...
'loggedin' => 'DATETIME NOT NULL', // time of last login
'loginip' => 'INT UNSIGNED NOT NULL', // INET_ATON of IP address of last login
......@@ -1430,13 +1431,13 @@
$keyrecalc['dorecalcpoints'] = true;
break;
// Up to here: Verison 1.7
// Up to here: Version 1.7
case 59:
// upgrade from alpha version removed
break;
// Up to here: Verison 1.7.1
// Up to here: Version 1.7.1
case 60:
// add new category widget - note title must match that from qa_register_core_modules()
......@@ -1459,8 +1460,13 @@
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);
......@@ -1523,4 +1529,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
......@@ -1181,7 +1181,7 @@
{
return 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)',
'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
......
......@@ -44,13 +44,22 @@
{
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();
}
......@@ -154,12 +163,19 @@
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(
'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$',
$salt, qa_db_calc_passcheck($password, $salt), $userid
);
qa_db_query_sub(
'UPDATE ^users SET passsalt=$, passcheck=UNHEX($) WHERE userid=$',
$salt, qa_db_calc_passcheck($password, $salt), $userid
);
}
}
......@@ -335,4 +351,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
......@@ -37,7 +37,7 @@
if (QA_FINAL_EXTERNAL_USERS)
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))
qa_redirect('login');
......@@ -52,10 +52,16 @@
qa_db_userfields_selectspec()
);
$changehandle=qa_opt('allow_change_usernames') || ((!$userpoints['qposts']) && (!$userpoints['aposts']) && (!$userpoints['cposts']));
$doconfirms=qa_opt('confirm_user_emails') && ($useraccount['level']<QA_USER_LEVEL_EXPERT);
$isconfirmed=($useraccount['flags'] & QA_USER_FLAGS_EMAIL_CONFIRMED) ? true : false;
$haspassword=isset($useraccount['passsalt']) && isset($useraccount['passcheck']);
$changehandle = qa_opt('allow_change_usernames') || (!$userpoints['qposts'] && !$userpoints['aposts'] && !$userpoints['cposts']);
$doconfirms = qa_opt('confirm_user_emails') && $useraccount['level'] < QA_USER_LEVEL_EXPERT;
$isconfirmed = ($useraccount['flags'] & QA_USER_FLAGS_EMAIL_CONFIRMED) ? true : false;
$haspasswordold = isset($useraccount['passsalt']) && isset($useraccount['passcheck']);
if (QA_PASSWORD_HASH) {
$haspassword = isset($useraccount['passhash']);
} else {
$haspassword = $haspasswordold;
}
$permit_error = qa_user_permit_error();
$isblocked = $permit_error !== false;
$pending_confirmation = $doconfirms && $permit_error == 'confirm';
......@@ -204,9 +210,18 @@
else {
$errors = array();
if ($haspassword && (strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck'])))
$errors['oldpassword'] = qa_lang('users/password_wrong');
$legacyPassError = strtolower(qa_db_calc_passcheck($inoldpassword, $useraccount['passsalt'])) != strtolower($useraccount['passcheck']);
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;
$errors = $errors + qa_password_validate($innewpassword1, $useraccount); // array union
......@@ -463,7 +478,7 @@
),
);
if (!$haspassword) {
if (!$haspassword && !$haspasswordold) {
$qa_content['form_password']['fields']['old']['type']='static';
$qa_content['form_password']['fields']['old']['value']=qa_lang_html('users/password_none');
}
......
......@@ -68,9 +68,30 @@
$inuserid=$matchusers[0];
$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';
qa_set_logged_in_user($inuserid, $userinfo['handle'], !empty($inremember));
$topath=qa_get('to');
......@@ -81,9 +102,7 @@
qa_redirect('account');
else
qa_redirect('');
} else
$errors['password']=qa_lang('users/password_wrong');
}
} else
$errors['emailhandle']=qa_lang('users/user_not_found');
......@@ -174,4 +193,4 @@
/*
Omit PHP closing tag to help avoid accidental output
*/
\ No newline at end of file
*/
......@@ -49,25 +49,6 @@
qa_initialize_php();
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
require_once QA_WORDPRESS_LOAD_FILE;
......@@ -196,6 +177,31 @@
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');
}
// 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