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 排除,勿推送到公开仓库。
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
namespace MRBS\Auth;
|
||||
|
||||
use MRBS\DB\DB;
|
||||
use MRBS\DB\DBExternalException;
|
||||
use MRBS\DB\DBFactory;
|
||||
use MRBS\User;
|
||||
use ValueError;
|
||||
|
||||
class AuthDbExt extends AuthDbAbstract
|
||||
{
|
||||
protected $password_format;
|
||||
protected $column_name_password;
|
||||
protected $column_name_email;
|
||||
protected $column_name_level;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
global $auth;
|
||||
|
||||
// Take our own copies of the settings
|
||||
$vars = array(
|
||||
'db_table',
|
||||
'password_format',
|
||||
'column_name_username',
|
||||
'column_name_display_name',
|
||||
'column_name_password',
|
||||
'column_name_email',
|
||||
'column_name_level'
|
||||
);
|
||||
|
||||
foreach ($vars as $var)
|
||||
{
|
||||
$this->$var = $auth['db_ext'][$var] ?? null;
|
||||
}
|
||||
|
||||
// Backwards compatibility setting
|
||||
if (!isset($this->password_format) && !empty($auth['db_ext']['use_md5_passwords']))
|
||||
{
|
||||
$this->password_format = 'md5';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function validateUser(
|
||||
#[\SensitiveParameter]
|
||||
?string $user,
|
||||
#[\SensitiveParameter]
|
||||
?string $pass)
|
||||
{
|
||||
// syntax_casesensitive_equals() modifies our SQL params array for us. We need an exact match -
|
||||
// MySQL allows trailing spaces when using an '=' comparison, eg 'john' = 'john '
|
||||
|
||||
$sql_params = array();
|
||||
|
||||
$query = "SELECT " . $this->connection()->quote($this->column_name_password) .
|
||||
"FROM " . $this->connection()->quote($this->db_table) .
|
||||
"WHERE " . $this->connection()->syntax_casesensitive_equals($this->column_name_username,
|
||||
$user,
|
||||
$sql_params);
|
||||
|
||||
$stmt = $this->connection()->query($query, $sql_params);
|
||||
|
||||
// Check whether (a) there's just one result and (b) that password matches
|
||||
return (($stmt->count() === 1) && $this->password_check($pass, $stmt->next_row()[0])) ? $user : false;
|
||||
}
|
||||
|
||||
|
||||
// Checks that a password matches a hash
|
||||
protected function password_check(string $password, string $hash) : bool
|
||||
{
|
||||
switch ($this->password_format)
|
||||
{
|
||||
case 'crypt':
|
||||
case 'password_hash':
|
||||
// Don't call password_needs_rehash() as (a) we may not have UPDATE rights on the external
|
||||
// database and (b) whether the password needs to be updated will depend on the PHP version
|
||||
// on the external system, not this one.
|
||||
return (password_verify($password, $hash));
|
||||
break;
|
||||
case 'plaintext':
|
||||
return hash_equals($hash, $password);
|
||||
break;
|
||||
default:
|
||||
// Check we've got a valid hashing algorithm. From PHP 8.0.0 hash() will do this.
|
||||
if ((version_compare(PHP_VERSION, '8.0.0') < 0) &&
|
||||
!in_array($this->password_format, hash_algos()))
|
||||
{
|
||||
throw new ValueError("'$this->password_format' is not a valid hashing algorithm");
|
||||
}
|
||||
return hash_equals($hash, hash($this->password_format, $password));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected function getUserFresh(string $username) : ?User
|
||||
{
|
||||
$sql_params = array();
|
||||
|
||||
// Only retrieve the columns we need (a) to minimise the query and (b) to avoid
|
||||
// sending unnecessary information unencrypted over the internet (Remote SQL is
|
||||
// usually unencrypted).
|
||||
$columns = array();
|
||||
|
||||
$properties = array(
|
||||
'column_name_display_name',
|
||||
'column_name_email',
|
||||
'column_name_level'
|
||||
);
|
||||
|
||||
foreach ($properties as $property)
|
||||
{
|
||||
if (isset($this->$property))
|
||||
{
|
||||
$columns[] = $this->$property;
|
||||
}
|
||||
}
|
||||
|
||||
$sql = "SELECT " . implode(', ', array_map(array($this->connection(), 'quote'), $columns)) . "
|
||||
FROM " . $this->connection()->quote($this->db_table) . "
|
||||
WHERE " . $this->connection()->syntax_casesensitive_equals($this->column_name_username,
|
||||
$username,
|
||||
$sql_params) . "
|
||||
LIMIT 1";
|
||||
|
||||
$stmt = $this->connection()->query($sql, $sql_params);
|
||||
|
||||
// The username doesn't exist - return NULL
|
||||
if ($stmt->count() === 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// The username does exist - return a User object
|
||||
$data = $stmt->next_row_keyed();
|
||||
|
||||
$user = new User($username);
|
||||
|
||||
// Set the email address
|
||||
if (isset($this->column_name_email) && isset($data[$this->column_name_email]))
|
||||
{
|
||||
$user->email = $data[$this->column_name_email];
|
||||
}
|
||||
|
||||
// Set the display name
|
||||
if (isset($this->column_name_display_name) && isset($data[$this->column_name_display_name]))
|
||||
{
|
||||
$user->display_name = $data[$this->column_name_display_name];
|
||||
}
|
||||
|
||||
// Set the level
|
||||
// First get the default level. Any admins defined in the config
|
||||
// file override settings in the external database.
|
||||
$user->level = $this->getDefaultLevel($username);
|
||||
|
||||
// Then if they are not an admin get their level from the external db
|
||||
if ($user->level < 2)
|
||||
{
|
||||
// If there's can entry in the db, then use that
|
||||
if (isset($this->column_name_level) &&
|
||||
($this->column_name_level !== '') &&
|
||||
isset($data[$this->column_name_level]))
|
||||
{
|
||||
$user->level = $data[$this->column_name_level];
|
||||
}
|
||||
}
|
||||
|
||||
// Then set the remaining properties. (We don't set all the properties from
|
||||
// $data initially because we want to preserve the default values if we don't
|
||||
// have data for the four important properties.)
|
||||
// (Note that normally there won't be any extra properties because we have
|
||||
// specified above the columns that we want, but this code is here so that extra
|
||||
// columns can be added if required.)
|
||||
foreach ($data as $key => $value)
|
||||
{
|
||||
if (!property_exists($user, $key))
|
||||
{
|
||||
$user->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
protected function connection(): ?DB
|
||||
{
|
||||
global $auth;
|
||||
|
||||
static $connection = null;
|
||||
|
||||
if (!isset($connection))
|
||||
{
|
||||
if (empty($auth['db_ext']['db_system']))
|
||||
{
|
||||
$auth['db_ext']['db_system'] = 'mysql';
|
||||
}
|
||||
|
||||
// Establish a connection
|
||||
$port = isset($auth['db_ext']['db_port']) ? (int) $auth['db_ext']['db_port'] : null;
|
||||
|
||||
try
|
||||
{
|
||||
$connection = DBFactory::create(
|
||||
$auth['db_ext']['db_system'],
|
||||
$auth['db_ext']['db_host'],
|
||||
$auth['db_ext']['db_username'],
|
||||
$auth['db_ext']['db_password'],
|
||||
$auth['db_ext']['db_name'],
|
||||
false,
|
||||
$port
|
||||
);
|
||||
}
|
||||
catch (\PDOException $e)
|
||||
{
|
||||
// Differentiate between an error in the MRBS database and the external database
|
||||
throw new DBExternalException($e->getMessage(), $e->getCode());
|
||||
}
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user