mirror of
https://github.com/avecms/AVE.cms.git
synced 2026-08-01 08:45:44 +00:00
143 lines
4.2 KiB
PHP
143 lines
4.2 KiB
PHP
<?php
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------------------
|
|
| AVE.cms
|
|
|--------------------------------------------------------------------------------------
|
|
| @package AVE.cms
|
|
| @file system/App/Common/IpBlocker.php
|
|
| @author AVE.cms <support@ave-cms.ru>
|
|
| @copyright 2007-2026 (c) AVE.cms
|
|
| @link https://ave-cms.ru
|
|
| @version 3.3
|
|
*/
|
|
|
|
namespace App\Common;
|
|
|
|
defined('BASEPATH') || die('Direct access to this location is not allowed.');
|
|
|
|
use App\Helpers\Request;
|
|
use App\Helpers\Response;
|
|
use DB;
|
|
|
|
class IpBlocker
|
|
{
|
|
public static function table()
|
|
{
|
|
return SystemTables::table('ip_blocks');
|
|
}
|
|
|
|
public static function enforce($maxAttempts = 120, $decaySeconds = 60)
|
|
{
|
|
$ip = Request::ip();
|
|
$block = self::findActive($ip, true);
|
|
if ($block) {
|
|
Response::setStatus(403);
|
|
header('Content-Type: text/plain; charset=UTF-8');
|
|
echo 'Доступ с этого IP-адреса заблокирован.';
|
|
exit;
|
|
}
|
|
|
|
if (self::bypassesAutomaticLimit()) {
|
|
return;
|
|
}
|
|
|
|
$key = 'public-request:' . $ip;
|
|
if (!RateLimiter::attempt($key, (int) $maxAttempts, (int) $decaySeconds)) {
|
|
$retry = max(1, RateLimiter::availableIn($key));
|
|
Response::setStatus(429);
|
|
header('Retry-After: ' . $retry);
|
|
header('Content-Type: text/plain; charset=UTF-8');
|
|
echo 'Слишком много запросов. Повторите попытку позже.';
|
|
exit;
|
|
}
|
|
}
|
|
|
|
protected static function bypassesAutomaticLimit()
|
|
{
|
|
try {
|
|
return Auth::systemUserCan('admin_panel');
|
|
} catch (\Throwable $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static function all($query = '')
|
|
{
|
|
$query = trim((string) $query);
|
|
$sql = 'SELECT * FROM `' . self::table() . '`';
|
|
if ($query !== '') {
|
|
$sql .= ' WHERE ip LIKE %ss OR reason LIKE %ss';
|
|
return DB::query($sql . ' ORDER BY created_at DESC, id DESC', $query, $query)->getAll() ?: array();
|
|
}
|
|
|
|
return DB::query($sql . ' ORDER BY created_at DESC, id DESC')->getAll() ?: array();
|
|
}
|
|
|
|
public static function block($ip, $reason, $expiresAt = null, $actorId = 0)
|
|
{
|
|
$ip = self::validateIp($ip);
|
|
$expiresAt = $expiresAt === null ? null : (int) $expiresAt;
|
|
if ($expiresAt !== null && $expiresAt <= time()) {
|
|
throw new \InvalidArgumentException('Срок блокировки должен быть в будущем');
|
|
}
|
|
|
|
if ($expiresAt === null) {
|
|
DB::query(
|
|
'INSERT INTO `' . self::table() . '` (ip, reason, expires_at, actor_id, created_at)'
|
|
. ' VALUES (%s, %s, NULL, %i, %i) ON DUPLICATE KEY UPDATE reason=VALUES(reason),'
|
|
. ' expires_at=NULL, actor_id=VALUES(actor_id), created_at=VALUES(created_at)',
|
|
$ip, trim((string) $reason), (int) $actorId, time()
|
|
);
|
|
} else {
|
|
DB::query(
|
|
'INSERT INTO `' . self::table() . '` (ip, reason, expires_at, actor_id, created_at)'
|
|
. ' VALUES (%s, %s, %i, %i, %i) ON DUPLICATE KEY UPDATE reason=VALUES(reason),'
|
|
. ' expires_at=VALUES(expires_at), actor_id=VALUES(actor_id), created_at=VALUES(created_at)',
|
|
$ip, trim((string) $reason), $expiresAt, (int) $actorId, time()
|
|
);
|
|
}
|
|
|
|
return self::findActive($ip);
|
|
}
|
|
|
|
public static function unblock($id)
|
|
{
|
|
return (bool) DB::query('DELETE FROM `' . self::table() . '` WHERE id = %i', (int) $id);
|
|
}
|
|
|
|
public static function findActive($ip, $failSilently = false)
|
|
{
|
|
$ip = self::validateIp($ip);
|
|
try {
|
|
$row = DB::query(
|
|
'SELECT * FROM `' . self::table() . '` WHERE ip = %s AND (expires_at IS NULL OR expires_at > %i) LIMIT 1',
|
|
$ip, time()
|
|
)->getAssoc();
|
|
} catch (\Throwable $e) {
|
|
if ($failSilently) { return null; }
|
|
throw $e;
|
|
}
|
|
|
|
return $row ? (array) $row : null;
|
|
}
|
|
|
|
public static function pruneExpired($limit = 250)
|
|
{
|
|
$limit = max(1, min(1000, (int) $limit));
|
|
DB::query('DELETE FROM `' . self::table() . '` WHERE expires_at IS NOT NULL AND expires_at <= %i LIMIT ' . $limit, time());
|
|
return (int) DB::affectedRows();
|
|
}
|
|
|
|
protected static function validateIp($ip)
|
|
{
|
|
$ip = trim((string) $ip);
|
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
|
throw new \InvalidArgumentException('Некорректный IP-адрес');
|
|
}
|
|
|
|
return $ip;
|
|
}
|
|
|
|
}
|