155 lines
6.1 KiB
PHP
155 lines
6.1 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace app\common\event;
|
||
|
||
/**
|
||
* 后台安全事件(数据回收、敏感数据记录)
|
||
* 迁移自 app/common/event/Security.php,需 Admin、Token、DataRecycle 等模型支持
|
||
*/
|
||
class Security
|
||
{
|
||
protected array $listenAction = ['edit', 'del'];
|
||
|
||
/**
|
||
* @param mixed $auth Auth 实例(backendInit 传入)
|
||
*/
|
||
public function handle(mixed $auth): bool
|
||
{
|
||
$request = function_exists('request') ? request() : null;
|
||
if (!$request) {
|
||
return true;
|
||
}
|
||
|
||
$action = $this->getActionFromPath($request->path());
|
||
if (!in_array($action, $this->listenAction)) {
|
||
return true;
|
||
}
|
||
if ($action === 'del' && $request->method() !== 'POST' && $request->method() !== 'DELETE') {
|
||
return true;
|
||
}
|
||
if ($action === 'edit' && $request->method() !== 'POST') {
|
||
return true;
|
||
}
|
||
|
||
if (!class_exists(\app\admin\model\DataRecycle::class) || !class_exists(\app\admin\model\SensitiveData::class)) {
|
||
return true;
|
||
}
|
||
|
||
if ($action === 'del') {
|
||
return $this->handleDel($request, $auth);
|
||
}
|
||
return $this->handleEdit($request, $auth);
|
||
}
|
||
|
||
protected function getActionFromPath(string $path): string
|
||
{
|
||
$parts = explode('/', trim($path, '/'));
|
||
return $parts[array_key_last($parts)] ?? '';
|
||
}
|
||
|
||
protected function handleDel($request, $auth): bool
|
||
{
|
||
$dataIds = $request->post('ids') ?? $request->get('ids');
|
||
if (!$dataIds) {
|
||
return true;
|
||
}
|
||
try {
|
||
$controllerPath = get_controller_path($request) ?? '';
|
||
$recycle = \app\admin\model\DataRecycle::where('status', 1)
|
||
->where('controller_as', $controllerPath)
|
||
->find();
|
||
if (!$recycle) {
|
||
return true;
|
||
}
|
||
$connection = class_exists(\ba\TableManager::class)
|
||
? \ba\TableManager::getConnection($recycle['connection'])
|
||
: null;
|
||
$db = $connection ? \support\think\Db::connect($connection) : \support\think\Db::connect();
|
||
$recycleData = $db->name($recycle['data_table'])
|
||
->whereIn($recycle['primary_key'], $dataIds)
|
||
->select()
|
||
->toArray();
|
||
$recycleDataArr = [];
|
||
$adminId = $auth && $auth->isLogin() ? $auth->id : 0;
|
||
foreach ($recycleData as $item) {
|
||
$recycleDataArr[] = [
|
||
'admin_id' => $adminId,
|
||
'recycle_id' => $recycle['id'],
|
||
'data' => json_encode($item, JSON_UNESCAPED_UNICODE),
|
||
'connection' => $recycle['connection'],
|
||
'data_table' => $recycle['data_table'],
|
||
'primary_key' => $recycle['primary_key'],
|
||
'ip' => $request->getRealIp(),
|
||
'useragent' => substr($request->header('user-agent', ''), 0, 255),
|
||
];
|
||
}
|
||
if ($recycleDataArr) {
|
||
$model = new \app\admin\model\DataRecycleLog();
|
||
$model->saveAll($recycleDataArr);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
\support\Log::warning('[DataSecurity] ' . $e->getMessage());
|
||
}
|
||
return true;
|
||
}
|
||
|
||
protected function handleEdit($request, $auth): bool
|
||
{
|
||
try {
|
||
$controllerPath = get_controller_path($request) ?? '';
|
||
$sensitiveData = \app\admin\model\SensitiveData::where('status', 1)
|
||
->where('controller_as', $controllerPath)
|
||
->find();
|
||
if (!$sensitiveData) {
|
||
return true;
|
||
}
|
||
$sensitiveData = $sensitiveData->toArray();
|
||
$dataId = $request->post($sensitiveData['primary_key']) ?? $request->get($sensitiveData['primary_key']);
|
||
$connection = class_exists(\ba\TableManager::class)
|
||
? \ba\TableManager::getConnection($sensitiveData['connection'])
|
||
: null;
|
||
$db = $connection ? \support\think\Db::connect($connection) : \support\think\Db::connect();
|
||
$editData = $db->name($sensitiveData['data_table'])
|
||
->field(array_keys($sensitiveData['data_fields']))
|
||
->where($sensitiveData['primary_key'], $dataId)
|
||
->find();
|
||
if (!$editData) {
|
||
return true;
|
||
}
|
||
$newData = $request->post();
|
||
$sensitiveDataLog = [];
|
||
foreach ($sensitiveData['data_fields'] as $field => $title) {
|
||
if (isset($editData[$field]) && isset($newData[$field]) && $editData[$field] != $newData[$field]) {
|
||
$newVal = $newData[$field];
|
||
if (stripos($field, 'password') !== false && $newVal) {
|
||
$newVal = '******';
|
||
}
|
||
$sensitiveDataLog[] = [
|
||
'admin_id' => $auth && $auth->isLogin() ? $auth->id : 0,
|
||
'sensitive_id' => $sensitiveData['id'],
|
||
'connection' => $sensitiveData['connection'],
|
||
'data_table' => $sensitiveData['data_table'],
|
||
'primary_key' => $sensitiveData['primary_key'],
|
||
'data_field' => $field,
|
||
'data_comment' => $title,
|
||
'id_value' => $dataId,
|
||
'before' => $editData[$field],
|
||
'after' => $newVal,
|
||
'ip' => $request->getRealIp(),
|
||
'useragent' => substr($request->header('user-agent', ''), 0, 255),
|
||
];
|
||
}
|
||
}
|
||
if ($sensitiveDataLog) {
|
||
$model = new \app\admin\model\SensitiveDataLog();
|
||
$model->saveAll($sensitiveDataLog);
|
||
}
|
||
} catch (\Throwable $e) {
|
||
\support\Log::warning('[DataSecurity] ' . $e->getMessage());
|
||
}
|
||
return true;
|
||
}
|
||
}
|