Files
mrbs-equbao-2026/change_password.php
人事系统开发 1ba6efd8ed MRBS 1.12.2 等保2.0二级整改完整提交
包含:登录失败锁定、90天密码有效期、30分钟会话超时、
强制改密、登录审计日志、屏幕水印、企业背景图、
备案信息固定底部、favicon、JS空集合保护、
会话过期体验优化(403 JSON)、display_errors 关闭、
固定 key 根治 Integrity check failed 等全部改动

注意:config.inc.php/.htaccess/.user.ini 含敏感信息,
通过 .gitignore 排除,勿推送到公开仓库。
2026-09-09 16:55:02 +08:00

277 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
namespace MRBS;
use MRBS\Audit;
use MRBS\Form\Element;
use MRBS\Form\ElementFieldset;
use MRBS\Form\ElementP;
use MRBS\Form\FieldDiv;
use MRBS\Form\FieldInputPassword;
use MRBS\Form\FieldInputSubmit;
use MRBS\Form\Form;
require "defaultincludes.inc";
// ===== 等保2.0二级整改:自助修改密码页 =====
// - 仅限已登录用户(未登录自动引导到登录页,登录后回到本页)
// - 校验当前密码 → 新密码复杂度($pwd_policy)→ 两次一致 → 新旧不同
// - 成功后写 password_changed_at(供 90 天有效期计算)并清除强制改密标记
// - 所有成功/失败动作写入安全审计日志(Audit)
function generate_change_password_form(?string $error = null, string $target_url = 'index.php') : void
{
global $pwd_policy;
$form = new Form(Form::METHOD_POST);
$form->setAttributes(array(
'class' => 'standard',
'id' => 'change_password',
'action' => multisite('change_password.php')
));
$form->addHiddenInputs(array(
'action' => 'change_password',
'target_url' => $target_url
));
$fieldset = new ElementFieldset();
$fieldset->addLegend(get_vocab('change_password'));
// 顶部提示 / 错误消息
$field = new FieldDiv();
$p = new ElementP();
if (isset($error))
{
switch ($error)
{
case 'old_pwd_invalid':
$p->setText(get_vocab('old_pwd_invalid'));
break;
case 'pwd_not_match':
$p->setText(get_vocab('passwords_not_eq'));
break;
case 'pwd_same':
$p->setText(get_vocab('pwd_same_as_old'));
break;
case 'pwd_invalid':
$p->setText(get_vocab('password_invalid'));
break;
default:
$p->setText(get_vocab('unknown_user'));
break;
}
$p->setAttribute('class', 'error');
$field->addControlElement($p);
// 策略不满足时列出具体规则
if (($error == 'pwd_invalid') && isset($pwd_policy))
{
$ul = new Element('ul');
$ul->setAttribute('class', 'error');
foreach ($pwd_policy as $rule => $value)
{
if ($value != 0)
{
$li = new Element('li');
$li->setText(get_vocab('policy_' . $rule, $value));
$ul->addElement($li);
}
}
$field->addControlElement($ul);
}
}
else
{
// 提示(强制改密或常规自助修改)
$p->setText(get_vocab('pwd_expired_msg'));
$field->addControlElement($p);
}
$fieldset->addElement($field);
// 当前密码
$field = new FieldInputPassword();
$field->setLabel(get_vocab('current_password'))
->setControlAttributes(array('id' => 'password_old',
'name' => 'password_old',
'autocomplete' => 'current-password',
'required' => true,
'autofocus' => true));
$fieldset->addElement($field);
// 新密码(输入两次)
$labels = array(get_vocab('new_password'), get_vocab('confirm_password'));
for ($i = 0; $i < 2; $i++)
{
$field = new FieldInputPassword();
$field->setLabel($labels[$i])
->setControlAttributes(array('id' => "password$i",
'name' => "password$i",
'autocomplete' => 'new-password',
'required' => true));
$fieldset->addElement($field);
}
// 口令策略说明
if (isset($pwd_policy))
{
$field = new FieldDiv();
$p = new ElementP();
$p->setText(get_vocab('pwd_must_contain'));
$field->addControlElement($p);
$ul = new Element('ul');
$ul->setAttribute('id', 'pwd_policy');
foreach ($pwd_policy as $rule => $value)
{
if ($value != 0)
{
$li = new Element('li');
$li->setText(get_vocab('policy_' . $rule, $value));
$ul->addElement($li);
}
}
$field->addControlElement($ul);
$fieldset->addElement($field);
}
$form->addElement($fieldset);
// 提交按钮
$fieldset = new ElementFieldset();
$field = new FieldInputSubmit();
$field->setControlAttributes(array('value' => get_vocab('change_password')));
$fieldset->addElement($field);
$form->addElement($fieldset);
$form->render();
}
function generate_change_password_success(string $target_url) : void
{
echo "<h2>" . get_vocab('change_password') . "</h2>\n";
echo "<p class=\"notice\">" . get_vocab('password_changed') . "</p>\n";
echo '<p><a href="' . htmlspecialchars(multisite($target_url)) . '">' . get_vocab('back') . "</a></p>\n";
}
// ===== 主流程 =====
// 必须是已登录用户
$mrbs_user = session()->getCurrentUser();
if (!isset($mrbs_user))
{
// 未登录:引导到登录页,登录成功后回到本页
session()->authGet(null, 'change_password.php');
exit;
}
// 跳转目标(仅允许站内相对 URL)
$target_url = get_form_var('target_url', 'url_local', null, INPUT_GET);
if (!isset($target_url) || ($target_url == ''))
{
$target_url = 'index.php';
}
// 防止把改密页自身作为跳转目标(避免循环)
if ($target_url == 'change_password.php')
{
$target_url = 'index.php';
}
// 处理提交(action 只从 POST 读取,且必须通过 CSRF 校验)
$action = get_form_var('action', 'string', null, INPUT_POST);
if (isset($action) && ($action == 'change_password'))
{
Form::checkToken();
$old_password = get_form_var('password_old', 'string', null, INPUT_POST);
$password0 = get_form_var('password0', 'string', null, INPUT_POST);
$password1 = get_form_var('password1', 'string', null, INPUT_POST);
$post_target = get_form_var('target_url', 'url_local', null, INPUT_POST);
if (isset($post_target) && ($post_target != ''))
{
$target_url = $post_target;
}
$error = null;
// 1. 校验当前密码
// (注意:validateUser 失败会计入失败次数;连续 5 次错误当前账号将被临时锁定,
// 与登录通道行为一致,属预期安全设计)
if (($old_password === null) || ($old_password === '') ||
!auth()->validateUser($mrbs_user->username, $old_password))
{
$error = 'old_pwd_invalid';
Audit::log('PWD_CHANGE_FAIL', $mrbs_user->username, 'old password incorrect');
}
// 2. 两次输入一致
elseif ($password0 !== $password1)
{
$error = 'pwd_not_match';
}
// 3. 符合复杂度策略
elseif (($password0 === null) || ($password0 === '') ||
!auth()->validatePassword($password0))
{
$error = 'pwd_invalid';
}
// 4. 新旧密码不同
elseif ($password0 === $old_password)
{
$error = 'pwd_same';
}
else
{
// 成功:更新口令并记录修改时间
auth()->updatePassword($mrbs_user->username, $password0);
Audit::log('PWD_CHANGE', $mrbs_user->username, 'self-service change');
// 清除“强制改密”标记(须在 session_write_close 前完成)
unset($_SESSION['mrbs_force_pwd_change']);
session_write_close();
location_header('change_password.php?result=ok&target_url=' . urlencode($target_url));
exit;
}
// 校验失败:回到表单显示错误(PRG 模式,防止表单重复提交)
location_header('change_password.php?error=' . urlencode($error) . '&target_url=' . urlencode($target_url));
exit;
}
// ===== 渲染页面 =====
$context = array(
'view' => $view,
'view_all' => $view_all,
'year' => $year,
'month' => $month,
'day' => $day,
'area' => isset($area) ? $area : null,
'room' => isset($room) ? $room : null
);
print_header($context);
$result = get_form_var('result', 'string', null, INPUT_GET);
$error = get_form_var('error', 'string', null, INPUT_GET);
echo "<div class=\"contents\">\n";
if (isset($result) && ($result == 'ok'))
{
generate_change_password_success($target_url);
}
else
{
generate_change_password_form($error, $target_url);
}
echo "</div>\n";
print_footer();