Skip to content

Commit

Permalink
Ticket #4797 - Need Change Password feature always asks to change pas…
Browse files Browse the repository at this point in the history
…sword.
  • Loading branch information
AntonLV committed Sep 11, 2024
1 parent 9d9dc21 commit 1867f0b
Show file tree
Hide file tree
Showing 9 changed files with 189 additions and 175 deletions.
101 changes: 45 additions & 56 deletions inc/classes/BxDolAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,14 @@ public function updatePhoneConfirmed($isConfirmed, $iAccountId = false)
*/
public function updatePassword($sPassword, $iAccountId = false)
{
$iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;

$sSalt = genRndSalt();
$sPasswordHash = encryptUserPwd($sPassword, $sSalt);
$iId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
$oAccountSender = BxDolAccount::getInstance();

$this->_oQuery->logPassword($iId);
$iPasswordExpired = $this->getPasswordExpiredDateByAccount($iAccountId);

if((int)$this->_oQuery->updatePassword($sPasswordHash, $sSalt, $iId, $iPasswordExpired) > 0) {
if((int)$this->_oQuery->updatePassword($sPasswordHash, $sSalt, $iId) > 0) {
/**
* @hooks
* @hookdef hook-account-edited 'account', 'edited' - hook on account edited $oAccount->updatePassword
Expand All @@ -311,10 +310,14 @@ public function updatePassword($sPassword, $iAccountId = false)
* - `action` - [string] action's name, can be reset_password
* @hook @ref hook-account-edited
*/
bx_alert('account', 'edited', $iId, $oAccountSender ? $oAccountSender->id() : $iId, array('action' => 'reset_password'));
bx_alert('account', 'edited', $iId, ($iSenderId = getLoggedId()) != 0 ? $iSenderId : $iId, [
'action' => 'reset_password'
]);

$this->doAudit($iId, '_sys_audit_action_account_reset_password');
return true;
}

return false;
}
/**
Expand Down Expand Up @@ -847,54 +850,32 @@ public function getEmailHash($iAccountId = false)
return md5($a['email'] . $a['salt'] . BX_DOL_SECRET);
}

public function getPasswordExpiredDate($iPasswordExpiredForMembership, $iAccountId = false)
public function getPasswordChangedDate($mixedAccount = false)
{
if ($iPasswordExpiredForMembership == 0)
return 0;

$iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;

$aAccountInfo = $this->_oQuery->getInfoById($iAccountId);

$iLastPassChanged = $this->_oQuery->getLastPasswordChanged($iAccountId);
if ($iLastPassChanged == 0)
$iLastPassChanged = $aAccountInfo['added'];
if(($bEmpty = empty($mixedAccount)) || !is_array($mixedAccount))
$mixedAccount = $this->_oQuery->getInfoById(!$bEmpty ? (int)$mixedAccount : $this->_iAccountID);

return $iPasswordExpiredForMembership * 86400 + $iLastPassChanged;
$iLastChanged = (int)$mixedAccount['password_changed'];
return $iLastChanged ? $iLastChanged : (int)$mixedAccount['added'];
}
public function getPasswordExpiredDateByAccount($iAccountId = false)

public function getPasswordExpiredDate($iPasswordExpiredForMembership, $mixedAccount = false)
{
$iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;

$oACL = BxDolAcl::getInstance();

$aProfiles = BxDolAccount::getInstance($iAccountId)->getProfiles();
$iPasswordExpiredForMembership = 0;
foreach ($aProfiles as $aProfile) {
$aMembersipInfo = $oACL->getMemberMembershipInfo($aProfile['id']);
$Memberships = [];
BxDolAclQuery::getInstance()->getLevels(['type' => 'by_id', 'value' => $aMembersipInfo['id']], $aMembership);
if($aMembership['password_expired'] > 0){
if ($iPasswordExpiredForMembership > 0 && $aMembership['password_expired'] < $iExpired)
$iPasswordExpiredForMembership = $aMembership['password_expired'];
if ($iPasswordExpiredForMembership == 0 )
$iPasswordExpiredForMembership = $aMembership['password_expired'];
}
}
if($iPasswordExpiredForMembership == 0)
return 0;

return $this->getPasswordExpiredDate($iPasswordExpiredForMembership, $iAccountId);
return $iPasswordExpiredForMembership * 86400 + $this->getPasswordChangedDate($mixedAccount);
}

public function isNeedChangePassword($iAccountId = false, $oInformer = false)
{
$iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;

$aAccountInfo = $this->getInfo();

list($sPageLink, $aPageParams) = bx_get_base_url_inline();
$bNeedRedirectToChangePassword = true;

if (isset($aPageParams['i']) && $aPageParams['i'] == 'account-settings-password')
$sChangePasswordUri = 'account-settings-password';
$bNeedRedirectToChangePassword = true;
if(isset($aPageParams['i']) && $aPageParams['i'] == $sChangePasswordUri)
$bNeedRedirectToChangePassword = false;

/**
Expand All @@ -908,31 +889,39 @@ public function isNeedChangePassword($iAccountId = false, $oInformer = false)
* - `override_result` - [bool] by ref, if Need Redirect To Change Password = true, otherwise = false, can be overridden in hook processing
* @hook @ref hook-account-is_need_to_change_password
*/
bx_alert('account', 'is_need_to_change_password', $iAccountId, false, ['override_result' => &$bNeedRedirectToChangePassword]);

if ($aAccountInfo['password_expired'] > 0 && $aAccountInfo['password_expired'] < time() && $bNeedRedirectToChangePassword) {
if (getParam('sys_account_accounts_force_password_change_after_expiration') == 'on'){
header('Location: ' . BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=account-settings-password'));
bx_alert('account', 'is_need_to_change_password', $iAccountId, false, [
'override_result' => &$bNeedRedirectToChangePassword
]);

if(!$bNeedRedirectToChangePassword)
return;

$aAccountInfo = $this->getInfo();
$aMembershipInfo = BxDolAcl::getInstance()->getMemberMembershipInfo($aAccountInfo['profile_id']);
$sChangePasswordUrl = BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=' . $sChangePasswordUri);

if(($iPasswordExpiredDate = $this->getPasswordExpiredDate($aMembershipInfo['password_expired'], $aAccountInfo)) && $iPasswordExpiredDate < time()) {
if(getParam('sys_account_accounts_force_password_change_after_expiration') == 'on') {
header('Location: ' . $sChangePasswordUrl);
exit;
}
else {
if(!$oInformer)
$oInformer = BxDolInformer::getInstance();

$oInformer->add('sys-account-need-to-change-password', _t('_sys_txt_account_need_to_change_password', BX_DOL_URL_ROOT . BxDolPermalinks::getInstance()->permalink('page.php?i=account-settings-password')), BX_INFORMER_ALERT);
$oInformer->add('sys-account-need-to-change-password', _t('_sys_txt_account_need_to_change_password', $sChangePasswordUrl), BX_INFORMER_ALERT);
}
}
}
public function doAudit($iAccountId, $sAction, $aData = array())

public function doAudit($iAccountId, $sAction, $aData = [])
{
$iAccountId = (int)$iAccountId ? (int)$iAccountId : $this->_iAccountID;
bx_audit(
$iAccountId,
'bx_accounts',
$sAction,
array('content_title' => $this->getEmail(), 'data' => $aData)
);

bx_audit($iAccountId, 'bx_accounts', $sAction, [
'content_title' => $this->getEmail(),
'data' => $aData
]);
}

/**
Expand Down
50 changes: 24 additions & 26 deletions inc/classes/BxDolAccountQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,13 @@ public function isOnline($iId)
* @param $iAccountId - account id to update password for
* @return number of affected rows
*/
public function updatePassword($sPasswordHash, $sSalt, $iAccountId, $iPasswordExpired)
public function updatePassword($sPasswordHash, $sSalt, $iAccountId)
{
$sQuery = $this->prepare("UPDATE `sys_accounts` SET `password` = ?, `salt` = ?, `password_expired` = ? WHERE `id`= ?", $sPasswordHash, $sSalt, $iPasswordExpired, $iAccountId);
return $this->query($sQuery);
return $this->query("UPDATE `sys_accounts` SET `password` = :password, `salt` = :salt, `password_changed` = UNIX_TIMESTAMP() WHERE `id` = :id", [
'id' => $iAccountId,
'password' => $sPasswordHash,
'salt' => $sSalt
]);
}

/**
Expand All @@ -173,23 +176,24 @@ public function updatePassword($sPasswordHash, $sSalt, $iAccountId, $iPasswordEx
*/
public function logPassword($iAccountId)
{
$iCountPassword = (int)getParam('sys_account_accounts_password_log_count');

if ($iCountPassword > 0){
$sSql = $this->prepare("SELECT `password`, `salt` FROM `sys_accounts` WHERE `id` = ?", $iAccountId);
$aAccount = $this->getRow($sSql);

$sQuery = "INSERT INTO `sys_accounts_password` (`password`, `password_changed`, `salt`, `account_id`) VALUES(:password, :password_changed, :salt, :account_id)";
$aBindings = array(
'password' => $aAccount['password'],
'password_changed' => time(),
'salt' => $aAccount['salt'],
'account_id' => $iAccountId,
);
$this->query($sQuery, $aBindings);

$this->query($this->prepare("DELETE FROM `sys_accounts_password` WHERE `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM `sys_accounts_password` WHERE `account_id` = ? ORDER BY `password_changed` DESC LIMIT 0, " . getParam('sys_account_accounts_password_log_count') . ") a)", $iAccountId));
}
$iCount = (int)getParam('sys_account_accounts_password_log_count');
if($iCount <= 0)
return;

$aAccount = $this->getRow("SELECT `password`, `password_changed`, `salt` FROM `sys_accounts` WHERE `id` = :id", [
'id' => $iAccountId
]);

$this->query("INSERT INTO `sys_accounts_password` SET " . $this->arrayToSQL([
'account_id' => $iAccountId,
'password' => $aAccount['password'],
'password_changed' => $aAccount['password_changed'],
'salt' => $aAccount['salt'],
]));

$this->query("DELETE FROM `sys_accounts_password` WHERE `id` NOT IN (SELECT `id` FROM (SELECT `id` FROM `sys_accounts_password` WHERE `account_id` = :account_id ORDER BY `password_changed` DESC LIMIT 0, " . $iCount . ") AS `tap`)", [
'account_id' => $iAccountId
]);
}

public function getLastPasswordChanged($iAccountId)
Expand All @@ -204,12 +208,6 @@ public function getLastPasswordLog($iAccountId)
return $this->getAll($sSql);
}

public function updatePasswordExpired($iAccountId, $iPasswordExpired)
{
$sQuery = $this->prepare("UPDATE `sys_accounts` SET `password_expired` = ? WHERE `id`= ?", $iPasswordExpired, $iAccountId);
return $this->query($sQuery);
}

/**
* Update last logged in time
* @param int $iID account id
Expand Down
27 changes: 24 additions & 3 deletions inc/classes/BxDolAclQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ function getLevels($aParams, &$aItems, $bReturnCount = true)
case 'password_can_expired':
$sWhereClause .= "AND `tal`.`PasswordExpired` <> 0";
break;

case 'password_expired_notify':
$sWhereClause .= "AND `tal`.`PasswordExpired` <> 0 AND tal`.`PasswordExpiredNotify` <> 0";
break;

case 'all':
break;
Expand Down Expand Up @@ -239,6 +243,7 @@ function getLevelCurrent($iProfileId, $iTime = 0)
`sys_acl_levels`.`QuotaSize` AS `quota_size`,
`sys_acl_levels`.`QuotaNumber` AS `quota_number`,
`sys_acl_levels`.`QuotaMaxFileSize` AS `quota_max_file_size`,
`sys_acl_levels`.`PasswordExpired` AS `password_expired`,
UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateStarts`) as `date_starts`,
UNIX_TIMESTAMP(`sys_acl_levels_members`.`DateExpires`) as `date_expires`,
`sys_acl_levels_members`.`State` AS `state`,
Expand All @@ -263,7 +268,8 @@ function getLevelByIdCached($iLevel)
`tal`.`Name` AS `name`,
`tal`.`QuotaSize` AS `quota_size`,
`tal`.`QuotaNumber` AS `quota_number`,
`tal`.`QuotaMaxFileSize` AS `quota_max_file_size`
`tal`.`QuotaMaxFileSize` AS `quota_max_file_size`,
`tal`.`PasswordExpired` AS `password_expired`
FROM `sys_acl_levels` AS `tal`
WHERE `tal`.`ID`=?
LIMIT 1", $iLevel);
Expand Down Expand Up @@ -470,11 +476,26 @@ function getContentByActionAsSQLPart($sContentTable, $sContentField, $mixedActio
);
}

function getProfilesByMembership($mixedLevelId)
function getProfilesByMembership($mixedLevelId, $aParams = [])
{
$sSelectClause = $sJoinClause = $sWhereClause = '';

$aSqlParts = $this->getContentByLevelAsSQLPart('sys_profiles', 'id', $mixedLevelId);
if(isset($aSqlParts['join'], $aSqlParts['where'])) {
$sJoinClause = $aSqlParts['join'];
$sWhereClause = $aSqlParts['where'];
}

if(isset($aParams['password_expired'], $aParams['password_expired_notify'])) {
$iExpire = (int)$aParams['password_expired'] * 86400;
$iNotify = ($iExpire - (int)$aParams['password_expired_notify']) * 86400;

$sSelectClause .= ", `ta`.`email` AS `email`, IF(`ta`.`password_changed` <> 0, `ta`.`password_changed`, `ta`.`added`) + " . $iExpire . " AS `password_expired`";
$sJoinClause .= " LEFT JOIN `sys_accounts` AS `ta` ON `sys_profiles`.`account_id`=`ta`.`id`";
$sWhereClause .= " AND IF(`ta`.`password_changed` <> 0, `ta`.`password_changed`, `ta`.`added`) + " . $iNotify . " < UNIX_TIMESTAMP() AND IF(`ta`.`password_changed` <> 0, `ta`.`password_changed`, `ta`.`added`) + " . $iExpire . " >= UNIX_TIMESTAMP()";
}

return $this->getAll("SELECT `sys_profiles`.* FROM `sys_profiles`" . $aSqlParts['join'] . " WHERE 1" . $aSqlParts['where']);
return $this->getAll("SELECT `sys_profiles`.*" . $sSelectClause . " FROM `sys_profiles`" . $sJoinClause . " WHERE 1" . $sWhereClause);
}

function getProfilesByAction($mixedActionName, $aParams = [])
Expand Down
40 changes: 15 additions & 25 deletions inc/classes/BxDolCronAccount.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,32 @@ public function processing()
{
set_time_limit(0);
ignore_user_abort();

$aEmails = [];


/* password expired soon email */
bx_import('BxTemplAcl');

$oAclDb = BxDolAclQuery::getInstance();
$oAccountDb = BxDolAccountQuery::getInstance();

$aEmails = [];
$aMemberships = [];
$oAclDb->getLevels(['type' => 'password_can_expired'], $aMemberships, false);
$oAclDb->getLevels(['type' => 'password_expired_notify'], $aMemberships, false);

foreach($aMemberships as $aMembership) {
$aProfiles = $oAclDb->getProfilesByMembership([$aMembership['id']]);
$aProfiles = $oAclDb->getProfilesByMembership([$aMembership['id']], [
'password_expired' => $aMembership['password_expired'],
'password_expired_notify' => $aMembership['password_expired_notify']
]);

foreach($aProfiles as $aProfile) {
$oAccount = BxDolAccount::getInstance($aProfile['account_id']);
if(!$oAccount)
if(in_array($aProfile['email'], $aEmails))
continue;

$iPasswordExpired = $oAccount->getPasswordExpiredDate($aMembership['password_expired']);
$aAccountInfo = $oAccountDb->getInfoById($aProfile['account_id']);
$iLastPassChanged = $oAccountDb->getLastPasswordChanged($aProfile['account_id']);
if (
!in_array($aAccountInfo['email'], $aEmails)
&& ($aMembership['password_expired'] - $aMembership['password_expired_notify']) * 86400 + $iLastPassChanged < time()
&& $iPasswordExpired >= time()
){
$aPlus = array();
$aPlus['expired_date'] = date('d.m.Y', $iPasswordExpired);
$aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_AccountPasswordExpired', $aPlus);

sendMail($aAccountInfo['email'], $aTemplate['Subject'], $aTemplate['Body'], $aProfile['id']);
$aEmails[] = $aAccountInfo['email'];
}

$oAccountDb->updatePasswordExpired($aProfile['account_id'], $iPasswordExpired);
$aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('t_AccountPasswordExpired', [
'expired_date' => date('d.m.Y', $aProfile['password_expired'])
]);

sendMail($aProfile['email'], $aTemplate['Subject'], $aTemplate['Body'], $aProfile['id']);
$aEmails[] = $aProfile['email'];
}
}

Expand Down
2 changes: 1 addition & 1 deletion install/sql/system.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1378,7 +1378,7 @@ CREATE TABLE `sys_accounts` (
`referred` varchar(255) NOT NULL DEFAULT '',
`login_attempts` tinyint(4) NOT NULL DEFAULT '0',
`locked` tinyint(4) NOT NULL DEFAULT '0',
`password_expired` int(11) NOT NULL DEFAULT '0',
`password_changed` int(11) NOT NULL DEFAULT '0',
`active` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`(191)),
Expand Down
6 changes: 3 additions & 3 deletions modules/boonex/english/data/langs/system/en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2569,7 +2569,7 @@ If it is not enabled then please consider implement this optimization, since it
<p>{account_output}</p>
{email_footer}]]></string>
<string name="_sys_et_txt_body_account_password_expired"><![CDATA[{email_header}
<p>Your password will expired in {expired_date}</p>
<p>Your password will expire {expired_date}</p>
<p>Please, sign-in and change password</p>
{email_footer}]]></string>
<string name="_sys_et_txt_body_pruning"><![CDATA[{email_header}
Expand Down Expand Up @@ -2630,7 +2630,7 @@ If it is not enabled then please consider implement this optimization, since it
<string name="_sys_et_txt_name_account_change_status_suspended"><![CDATA[Account status changed to suspended]]></string>
<string name="_sys_et_txt_name_manage_approve"><![CDATA[Approve: Contact content's author]]></string>
<string name="_sys_et_txt_name_system_account"><![CDATA[New Accounts]]></string>
<string name="_sys_et_txt_name_system_account_password_expired"><![CDATA[Expired Password]]></string>
<string name="_sys_et_txt_name_system_account_password_expired"><![CDATA[Password will expire]]></string>
<string name="_sys_et_txt_name_system_pruning"><![CDATA[Pruning result]]></string>
<string name="_sys_et_txt_name_upgrade_failed"><![CDATA[Upgrade failed]]></string>
<string name="_sys_et_txt_name_upgrade_modules_failed"><![CDATA[Modules upgrade failed]]></string>
Expand All @@ -2653,7 +2653,7 @@ If it is not enabled then please consider implement this optimization, since it
<string name="_sys_et_txt_subject_account_change_status_suspended"><![CDATA[Account suspended]]></string>
<string name="_sys_et_txt_subject_manage_approve"><![CDATA[Approvement. A question about '{content_title}'.]]></string>
<string name="_sys_et_txt_subject_account"><![CDATA[{site_name} {account_count} accounts created yesterday]]></string>
<string name="_sys_et_txt_subject_account_password_expired"><![CDATA[{site_name} - your password will expired soon]]></string>
<string name="_sys_et_txt_subject_account_password_expired"><![CDATA[Your password will expire soon]]></string>
<string name="_sys_et_txt_subject_pruning"><![CDATA[{site_name} pruning result]]></string>
<string name="_sys_et_txt_subject_upgrade_failed"><![CDATA[{site_name} upgrade failed]]></string>
<string name="_sys_et_txt_subject_upgrade_modules_failed"><![CDATA[Modules upgrade failed on {site_name}]]></string>
Expand Down
Loading

0 comments on commit 1867f0b

Please sign in to comment.