1
0
mirror of https://github.com/e107inc/e107.git synced 2025-07-29 19:00:26 +02:00

Issue #595 - Use PHP 5.5+ password methods when available. User password field expanded to 255 chars. Fixes issue with salt password being updated via User > Edit User.

This commit is contained in:
Cameron
2016-06-06 19:54:48 -07:00
parent 32bd1d04ab
commit 127271e9fc
6 changed files with 229 additions and 86 deletions

View File

@@ -1375,7 +1375,23 @@ $text .= "
<tr>
<td><label for='passwordencoding'>".PRFLAN_188.":</label></td>
<td>
".$frm->radio_switch('passwordEncoding', varset($pref['passwordEncoding'], 0), PRFLAN_190, PRFLAN_189)."
";
$pwdEncodeOpts = array();
if(function_exists('password_verify')) // ie. php 5.5 or higher
{
$pwdEncodeOpts[3] = "PHP Default (Preferred)";
}
$pwdEncodeOpts[1] = PRFLAN_190;
$pwdEncodeOpts[0] = PRFLAN_189;
$text .= $frm->select('passwordEncoding', $pwdEncodeOpts, varset($pref['passwordEncoding'], 0));
// $text .= $frm->radio_switch('passwordEncoding', varset($pref['passwordEncoding'], 0), PRFLAN_190, PRFLAN_189);
$text .= "
<div class='smalltext field-help'>".PRFLAN_191."</div>
</td>
</tr>

View File

@@ -273,7 +273,7 @@ class users_admin_ui extends e_admin_ui
'user_loginname' => array('title' => LAN_USER_02, 'tab'=>0, 'type' => 'text', 'data'=>'str', 'width' => 'auto'), // User name
'user_login' => array('title' => LAN_USER_03, 'tab'=>0, 'type' => 'text', 'inline'=>true, 'data'=>'str', 'width' => 'auto'), // Real name (no real vetting)
'user_customtitle' => array('title' => LAN_USER_04, 'tab'=>0, 'type' => 'text', 'inline'=>true, 'data'=>'str', 'width' => 'auto'), // No real vetting
'user_password' => array('title' => LAN_PASSWORD, 'tab'=>0, 'type' => 'method', 'data'=>'str', 'width' => 'auto'), //TODO add md5 option to form handler?
'user_password' => array('title' => LAN_PASSWORD, 'tab'=>0, 'type' => 'method', 'data'=>'safestr', 'width' => 'auto'), //TODO add md5 option to form handler?
'user_sess' => array('title' => 'Session', 'tab'=>0, 'noedit'=>true, 'type' => 'text', 'width' => 'auto'), // Photo
'user_image' => array('title' => LAN_USER_07, 'tab'=>0, 'type' => 'dropdown', 'data'=>'str', 'width' => 'auto'), // Avatar
'user_email' => array('title' => LAN_EMAIL, 'tab'=>0, 'type' => 'text', 'inline'=>true, 'data'=>'str', 'width' => 'auto', 'writeParms'=>array('size'=>'xxlarge')),
@@ -485,7 +485,9 @@ class users_admin_ui extends e_admin_ui
}
else
{
$new_data['user_password'] = md5($new_data['user_password']); //TODO add support for salted passwords etc.
$new_data['user_password'] = e107::getUserSession()->HashPassword($new_data['user_password'], $new_data['user_login']);
e107::getMessage()->addDebug("Password Hash: ".$new_data['user_password']);
}
if(!empty($new_data['perms']))
@@ -1385,9 +1387,12 @@ class users_admin_ui extends e_admin_ui
}
$user_data['user_password'] = $userMethods->HashPassword($savePassword, $user_data['user_login']);
$user_data['user_join'] = time();
e107::getMessage()->addDebug("Password Hash: ".$user_data['user_password']);
if ($userMethods->needEmailPassword())
{
// Save separate password encryption for use with email address

View File

@@ -526,7 +526,7 @@ CREATE TABLE user (
user_name varchar(100) NOT NULL default '',
user_loginname varchar(100) NOT NULL default '',
user_customtitle varchar(100) NOT NULL default '',
user_password varchar(50) NOT NULL default '',
user_password varchar(255) NOT NULL default '',
user_sess varchar(100) NOT NULL default '',
user_email varchar(100) NOT NULL default '',
user_signature text NOT NULL,

View File

@@ -213,6 +213,12 @@ class userlogin
{ // May want to rewrite password using salted hash (or whatever the preferred method is) - $pass_result has the value to write
// If login by email address also allowed, will have to write that value too
// $sql->update('user',"`user_password` = '{$pass_result}' WHERE `user_id`=".intval($this->userData['user_id']));
if($this->userMethods->rehashPassword($this->userData,$userpass)!==false)
{
$log = e107::getLog();
$auditLog = "User Password ReHashed";
$log->user_audit(USER_AUDIT_LOGIN, $auditLog, $this->userData['user_id'], $this->userData['user_name']);
}
}
@@ -396,7 +402,7 @@ class userlogin
$qry[0] = "{$dbAlias}`user_loginname`= '".$tp->toDB($username)."'"; // username only (default)
$qry[1] = "{$dbAlias}`user_email` = '".$tp->toDB($username)."'"; // email only
$qry[2] = (strpos($username,'@') !== FALSE ) ? "{$dbAlias}`user_loginname`= '".$tp->toDB($username)."' OR {$dbAlias}`user_email` = '".$tp->toDB($username)."'" : $qry[0]; //username or email
$qry[2] = (strpos($username,'@') !== false ) ? "{$dbAlias}`user_loginname`= '".$tp->toDB($username)."' OR {$dbAlias}`user_email` = '".$tp->toDB($username)."'" : $qry[0]; //username or email
// Look up user in DB - even if email addresses allowed, still look up by user name as well - user could have specified email address for their login name
@@ -477,6 +483,7 @@ class userlogin
}
$auditLog['result'] = $pass_result;
$log->user_audit(USER_AUDIT_LOGIN, $auditLog, $this->userData['user_id'], $this->userData['user_name']);
}

View File

@@ -2299,7 +2299,7 @@ class e_front_model extends e_model
}
else //no db field types, use toDB()
{
$src_data = $tp->toDB($src_data);
$src_data = e107::getParser()->toDB($src_data);
}
}
@@ -2647,6 +2647,10 @@ class e_front_model extends e_model
return intval($this->toNumber($value));
break;
case 'safestr':
return $tp->filter($value);
break;
case 'str':
case 'string':
case 'array':

View File

@@ -2,15 +2,12 @@
/*
* e107 website system
*
* Copyright (C) 2008-2011 e107 Inc (e107.org)
* Copyright (C) 2008-2016 e107 Inc (e107.org)
* Released under the terms and conditions of the
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
*
* Handler - user-related functions
*
* $URL$
* $Id$
*
*/
@@ -18,11 +15,9 @@
*
* @package e107
* @subpackage e107_handlers
* @version $Id$;
*
* USER HANDLER CLASS - manages login and various user functions
*
* @todo - consider vetting of user_xup (if we keep it)
*/
@@ -40,11 +35,12 @@ define('USER_TEMPORARY_ACCOUNT', 5);
define('PASSWORD_E107_MD5',0);
define('PASSWORD_E107_SALT',1);
define('PASSWORD_E107_PHP', 3); // PHP Default - Using the bcrypt algorithm (default as of PHP 5.5.0).
define('PASSWORD_E107_ID','$E$'); // E107 salted
define('PASSWORD_INVALID', FALSE);
define('PASSWORD_INVALID', false);
define('PASSWORD_VALID',TRUE);
define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_MD5);
//define ('PASSWORD_DEFAULT_TYPE',PASSWORD_E107_SALT);
@@ -57,14 +53,21 @@ class UserHandler
var $userVettingInfo = array();
var $preferred = PASSWORD_DEFAULT_TYPE; // Preferred password format
var $passwordOpts = 0; // Copy of pref
var $passwordEmail = FALSE; // True if can use email address to log in
var $passwordEmail = false; // True if can use email address to log in
var $otherFields = array();
private $passwordAPI = false;
// Constructor
public function __construct()
{
$pref = e107::getPref();
e107::lan('core','user');
if(function_exists('password_verify'))
{
$this->passwordAPI = true;
}
/**
Table of vetting methods for user data - lists every field whose value could be set manually.
Valid 'vetMethod' values (use comma separated list for multiple vetting):
@@ -116,6 +119,7 @@ class UserHandler
'user_pwchange' => LAN_USER_24
// user_chats int(10) unsigned NOT NULL default '0',
);
$this->otherFieldTypes = array(
'user_join' => 'int',
'user_lastvisit' => 'int',
@@ -131,20 +135,27 @@ class UserHandler
);
$this->passwordOpts = varset($pref['passwordEncoding'], 0);
$this->passwordEmail = varset($pref['allowEmailLogin'],FALSE);
$this->passwordEmail = varset($pref['allowEmailLogin'], false);
switch ($this->passwordOpts)
{
case 3 :
$this->preferred = PASSWORD_E107_PHP;
break;
case 1 :
case 2 :
$this->preferred = PASSWORD_E107_SALT;
break;
case 0 :
default :
$this->preferred = PASSWORD_E107_MD5;
$this->passwordOpts = 0; // In case it got set to some stupid value
break;
}
return FALSE;
return false;
}
@@ -153,13 +164,17 @@ class UserHandler
*
* @param string $password - plaintext password as entered by user
* @param string $login_name - string used to log in (could actually be email address)
* @param empty|PASSWORD_E107_MD5|PASSWORD_E107_SALT $force - if non-empty, forces a particular type of password
* @param string $force empty| PASSWORD_E107_MD 5| PASSWORD_E107_SALT | PASSWORD_E107_PHP $force - if non-empty, forces a particular type of password
*
* @return string|boolean - FALSE if invalid emcoding method, else encoded password to store in DB
* @return string|boolean - false if invalid emcoding method, else encoded password to store in DB
*/
public function HashPassword($password, $login_name='', $force='')
public function HashPassword($password, $login_name='', $force=false)
{
if ($force == '') $force = $this->preferred;
if($force === false)
{
$force = $this->preferred;
}
switch ($force)
{
case PASSWORD_E107_MD5 :
@@ -168,8 +183,17 @@ class UserHandler
case PASSWORD_E107_SALT :
return PASSWORD_E107_ID.md5(md5($password).$login_name);
break;
case PASSWORD_E107_PHP :
if($this->passwordAPI)
{
return password_hash($password, PASSWORD_DEFAULT);
}
return FALSE;
break;
}
return false;
}
@@ -187,26 +211,113 @@ class UserHandler
*/
public function CheckPassword($password, $login_name, $stored_hash)
{
if (strlen(trim($password)) == 0) return PASSWORD_INVALID;
if (($this->passwordOpts <= 1) && (strlen($stored_hash) == 32))
{ // Its simple md5 encoding
if(empty(trim($password)))
{
return PASSWORD_INVALID;
}
$type = $this->getHashType($stored_hash);
switch($type)
{
case PASSWORD_E107_MD5:
if (md5($password) !== $stored_hash) return PASSWORD_INVALID;
if ($this->preferred == PASSWORD_E107_MD5) return PASSWORD_VALID;
return $this->HashPassword($password); // Valid password, but non-preferred encoding; return the new hash
break;
case PASSWORD_E107_SALT:
$hash = $this->HashPassword($password, $login_name, PASSWORD_E107_SALT);
if ($hash === false) return PASSWORD_INVALID;
return ($hash == $stored_hash) ? PASSWORD_VALID : PASSWORD_INVALID;
break;
case PASSWORD_E107_PHP: // PHP 5.5+ Blowfish+
if($this->passwordAPI === true && password_verify($password,$stored_hash))
{
return PASSWORD_VALID;
}
break;
}
// Allow the salted password even if disabled - for those that do try to go back!
// if (($this->passwordOpts >= 1) && (strlen($stored_hash) == 35) && (substr($stored_hash,0,3) == PASSWORD_E107_ID))
if ((strlen($stored_hash) == 35) && (substr($stored_hash,0,3) == PASSWORD_E107_ID))
{ // Its the standard E107 salted hash
$hash = $this->HashPassword($password, $login_name, PASSWORD_E107_SALT);
if ($hash === FALSE) return PASSWORD_INVALID;
return ($hash == $stored_hash) ? PASSWORD_VALID : PASSWORD_INVALID;
}
return PASSWORD_INVALID;
}
/**
* If necessary, rehash the user password to the currently set algorythm.
* @param array $user - user fields. required: user_id, user_loginname, user_password
* @param string $password - plain text password.
* @return bool|int
*/
public function rehashPassword($user, $password)
{
$type = $this->getHashType($user['user_password']);
if($type == $this->preferred || empty($user['user_id'] || empty($user['user_password']) || empty($user['user_loginname'])))
{
return false;
}
$sql = e107::getDb();
$update = array(
'data' => array(
'user_password' => $this->HashPassword($password, $user['user_loginname']),
),
'WHERE' => "user_id = ".intval($user['user_id'])." LIMIT 1",
'_FIELD_TYPES' => array('user_password' => 'safestr'),
);
return $sql->update('user', $update);
}
/**
* Detect Password Hash Algorythm type
* @param string $hash - Password hash to analyse
* @return bool|int
*/
public function getHashType($hash)
{
if(empty($hash))
{
return false;
}
if(($this->passwordOpts <= 1) && (strlen($hash) === 32))
{
return PASSWORD_E107_MD5;
}
if ((strlen($hash) === 35) && (substr($hash,0,3) == PASSWORD_E107_ID))
{
return PASSWORD_E107_SALT;
}
if($this->passwordAPI)
{
$info = password_get_info($hash);
if(!empty($info['algo']))
{
return PASSWORD_E107_PHP;
}
}
return false;
}
/**
* Reset the user's password with an auto-generated string.
* @param $uid
@@ -261,7 +372,7 @@ class UserHandler
if (strlen($stored_hash) == 32)
{ // Its simple md5 password storage
$stored_hash = PASSWORD_E107_ID.md5($stored_hash.$login_name); // Convert to the salted format always used by CHAP
if ($this->passwordOpts != PASSWORD_E107_MD5) $valid_ret = $stored_response;
if ($this->passwordOpts != PASSWORD_E107_MD5) $valid_ret = $stored_hash;
}
$testval = md5(substr($stored_hash,strlen(PASSWORD_E107_ID)).$challenge);
if ($testval == $response) return $valid_ret;
@@ -276,11 +387,11 @@ class UserHandler
*
* @param string $fieldName - name of field being changed
*
* @return bool TRUE if change required, FALSE otherwise
* @return bool TRUE if change required, false otherwise
*/
public function isPasswordRequired($fieldName)
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
if ($this->preferred == PASSWORD_E107_MD5) return false;
switch ($fieldName)
{
case 'user_email' :
@@ -288,7 +399,7 @@ class UserHandler
case 'user_loginname' :
return TRUE;
}
return FALSE;
return false;
}
@@ -300,9 +411,9 @@ class UserHandler
*/
public function needEmailPassword()
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
if ($this->preferred == PASSWORD_E107_MD5) return false;
if ($this->passwordEmail) return TRUE;
return FALSE;
return false;
}
@@ -311,13 +422,13 @@ class UserHandler
* Checks whether the password value can be converted to the current default
*
* @param string $password - hashed password
* @return bool TRUE if conversion possible, FALSE if not possible, or not needed.
* @return bool TRUE if conversion possible, false if not possible, or not needed.
*/
public function canConvert($password)
{
if ($this->preferred == PASSWORD_E107_MD5) return FALSE;
if ($this->preferred == PASSWORD_E107_MD5) return false;
if (strlen($password) == 32) return TRUE; // Can convert from md5 to salted
return FALSE;
return false;
}
@@ -332,7 +443,7 @@ class UserHandler
*/
public function ConvertPassword($password, $login_name)
{
if ($this->canConvert($password) === FALSE) return $password;
if ($this->canConvert($password) === false) return $password;
return PASSWORD_E107_ID.md5($password.$login_name);
}
@@ -448,7 +559,7 @@ class UserHandler
// (else just ignore other characters in pattern)
default :
if (strrpos($alphaNum, $c) !== FALSE)
if (strrpos($alphaNum, $c) !== false)
{
$newname .= $c;
}
@@ -474,8 +585,8 @@ class UserHandler
$tp = e107::getParser();
$tmp = strtolower($tp -> toDB(trim(substr($email, strrpos($email, "@")+1)))); // Pull out the domain name
if ($tmp == '') return FALSE;
if (strpos($tmp,'.') === FALSE) return FALSE;
if ($tmp == '') return false;
if (strpos($tmp,'.') === false) return false;
$em = array_reverse(explode('.',$tmp));
$line = '';
$out = array($fieldname."='*@{$tmp}'"); // First element looks for domain as email address
@@ -498,7 +609,7 @@ class UserHandler
*
* @return void
*/
public function makeUserCookie($lode,$autologin = FALSE)
public function makeUserCookie($lode,$autologin = false)
{
$cookieval = $lode['user_id'].'.'.md5($lode['user_password']); // (Use extra md5 on cookie value to obscure hashed value for password)
if (e107::getPref('user_tracking') == 'session')
@@ -533,7 +644,7 @@ class UserHandler
*
* @return array|string of userclass information according to $asArray
*/
public function addCommonClasses($userData, $asArray = FALSE, $incInherited = FALSE, $fromAdmin = FALSE)
public function addCommonClasses($userData, $asArray = false, $incInherited = false, $fromAdmin = false)
{
if ($incInherited)
{
@@ -568,7 +679,7 @@ class UserHandler
* @param bool $all if false, just returns modifiable fields. Else returns all
* @return array - key is field name, value is 'nice name' (descriptive name)
*/
public function getNiceNames($all = FALSE)
public function getNiceNames($all = false)
{
// $ret = array('user_id' => LAN_USER_13);
foreach ($this->userVettingInfo as $k => $v)
@@ -633,7 +744,7 @@ Following fields auto-filled in code as required:
*
* @param array $targetData - user data generated from earlier vetting stages - only the data in $targetData['data'] is checked
*
* @return bool TRUE if nothing updated; FALSE if errors found
* @return bool TRUE if nothing updated; false if errors found
*/
public function userValidation(&$targetData)
{
@@ -662,7 +773,7 @@ Following fields auto-filled in code as required:
{ // See if email address banned
$wc = e107::getIPHandler()->makeEmailQuery($v); // Generate the query for the ban list
if ($wc) { $wc = "`banlist_ip`='{$v}' OR ".$wc; }
if (($wc === FALSE) || !e107::getIPHandler()->checkBan($wc, FALSE, TRUE))
if (($wc === false) || !e107::getIPHandler()->checkBan($wc, false, TRUE))
{
// echo "Email banned<br />";
$errMsg = ERR_BANNED_EMAIL;
@@ -684,7 +795,7 @@ Following fields auto-filled in code as required:
{ // Update the error
$targetData['errors']['user_email'] = $errMsg;
$targetData['failed']['user_email'] = $v;
$ret = FALSE;
$ret = false;
}
return $ret;
}
@@ -697,7 +808,7 @@ Following fields auto-filled in code as required:
*
* @param array $userInfo - user data destined for the database
*
* @return bool TRUE if additions made, FALSE if no change.
* @return bool TRUE if additions made, false if no change.
*
* @todo - may be unnecessary with auto-generation of _NOTNULL array in db handler
*/
@@ -705,7 +816,7 @@ Following fields auto-filled in code as required:
{
// $nonDefaulted = array('user_signature' => '', 'user_prefs' => '', 'user_class' => '', 'user_perms' => '');
$nonDefaulted = array('user_signature' => '', 'user_prefs' => '', 'user_class' => '', 'user_perms' => '', 'user_realm' => ''); // Delete when McFly finished
$ret = FALSE;
$ret = false;
foreach ($nonDefaulted as $k => $v)
{
if (!isset($userInfo[$k]))
@@ -725,7 +836,7 @@ Following fields auto-filled in code as required:
*
* @return int number of user records deleted
*/
public function deleteExpired($force = FALSE)
public function deleteExpired($force = false)
{
$pref = e107::getPref();
$sql = e107::getDb();
@@ -828,13 +939,13 @@ Following fields auto-filled in code as required:
* @param integer $uid - internal user ID, zero if not known
* @param string $emailAddress - email address (optional)
*
* @return boolean | string - FALSE if user found, error message if not
* @return boolean | string - false if user found, error message if not
*/
public function userStatusUpdate($action, $uid, $emailAddress = '')
{
$db = e107::getDb('user');
$qry = '';
$error = FALSE; // Assume no error to start with
$error = false; // Assume no error to start with
$uid = intval($uid); // Precautionary - should have already been done
switch ($action)
{
@@ -859,7 +970,7 @@ Following fields auto-filled in code as required:
}
if ($uid) { $qry = '`user_id`='.$uid; }
if ($emailAddress) { if ($qry) $qry .= ' OR '; $qry .= "`user_email` = '{$emailAddress}'"; }
if (FALSE === $db->select('user', 'user_id, user_email, user_ban, user_loginname', $qry.' LIMIT 1'))
if (false === $db->select('user', 'user_id, user_email, user_ban, user_loginname', $qry.' LIMIT 1'))
{
$error = 'User not found: '.$uid.'/'.$emailAddress;
}
@@ -1106,7 +1217,7 @@ class e_user_provider
$userdata['user_lastvisit'] = 0;
$userdata['user_currentvisit'] = 0;
$userdata['user_comments'] = 0;
$userdata['user_ip'] = e107::getIPHandler()->getIP(FALSE);
$userdata['user_ip'] = e107::getIPHandler()->getIP(false);
$userdata['user_ban'] = USER_VALIDATED;
$userdata['user_prefs'] = '';
$userdata['user_visits'] = 0;
@@ -1366,7 +1477,7 @@ class e_userperms
if($plg->parse_plugin($row2['plugin_path']))
{
$plug_vars = $plg->plug_vars;
$this->plugin_perms[("P".$row2['plugin_id'])] = array($tp->toHTML($row2['plugin_name'], FALSE, 'RAWTEXT,defs'));
$this->plugin_perms[("P".$row2['plugin_id'])] = array($tp->toHTML($row2['plugin_name'], false, 'RAWTEXT,defs'));
$this->plugin_perms[("P".$row2['plugin_id'])][1] = $plg->getIcon($row2['plugin_path'],16);
$this->plugin_perms[("P".$row2['plugin_id'])][2] = $plg->getIcon($row2['plugin_path'],32);
}
@@ -1377,7 +1488,7 @@ class e_userperms
// $sql->db_Select("plugin", "*", "plugin_installflag='1'");
// while ($row2 = $sql->db_Fetch())
// {
// $this->plugin_perms[("P".$row2['plugin_id'])] = array($tp->toHTML($row2['plugin_name'], FALSE, 'RAWTEXT,defs'));
// $this->plugin_perms[("P".$row2['plugin_id'])] = array($tp->toHTML($row2['plugin_name'], false, 'RAWTEXT,defs'));
// $this->plugin_perms[("P".$row2['plugin_id'])][1] = $plg->getIcon('forum')
// }