Skip to content

Commit cf6afdb

Browse files
authored
Merge pull request #2 from mageplaza/2.4-develop
2.4 develop
2 parents a989394 + d7e8941 commit cf6afdb

File tree

3 files changed

+386
-0
lines changed

3 files changed

+386
-0
lines changed

Cron/SaveAttackIP.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
/**
3+
* Mageplaza
4+
*
5+
* NOTICE OF LICENSE
6+
*
7+
* This source file is subject to the mageplaza.com license that is
8+
* available through the world-wide-web at this URL:
9+
* https://www.mageplaza.com/LICENSE.txt
10+
*
11+
* DISCLAIMER
12+
*
13+
* Do not edit or add to this file if you wish to upgrade this extension to newer
14+
* version in the future.
15+
*
16+
* @category Mageplaza
17+
* @package Mageplaza_DDoSProtect
18+
* @copyright Copyright (c) Mageplaza (https://www.mageplaza.com/)
19+
* @license https://www.mageplaza.com/LICENSE.txt
20+
*/
21+
22+
namespace Mageplaza\DDoSProtect\Cron;
23+
24+
use Mageplaza\DDoSProtect\Helper\DDos;
25+
26+
/**
27+
* Class CheckIP
28+
* @package Mageplaza\DDoSProtect\Cron
29+
*/
30+
class SaveAttackIP
31+
{
32+
/**
33+
* @var DDos
34+
*/
35+
private $helperDDoS;
36+
37+
/**
38+
* CheckIP constructor.
39+
*
40+
* @param DDos $DDosHelper
41+
*/
42+
public function __construct(
43+
DDos $DDosHelper
44+
) {
45+
$this->helperDDoS = $DDosHelper;
46+
}
47+
48+
/**
49+
* @return $this
50+
*/
51+
public function execute()
52+
{
53+
$this->helperDDoS->saveIpAttackToDB();
54+
55+
return $this;
56+
}
57+
}

Helper/DDos.php

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
<?php
2+
/**
3+
* Mageplaza
4+
*
5+
* NOTICE OF LICENSE
6+
*
7+
* This source file is subject to the mageplaza.com license that is
8+
* available through the world-wide-web at this URL:
9+
* https://www.mageplaza.com/LICENSE.txt
10+
*
11+
* DISCLAIMER
12+
*
13+
* Do not edit or add to this file if you wish to upgrade this extension to newer
14+
* version in the future.
15+
*
16+
* @category Mageplaza
17+
* @package Mageplaza_DDoSProtect
18+
* @copyright Copyright (c) Mageplaza (https://www.mageplaza.com/)
19+
* @license https://www.mageplaza.com/LICENSE.txt
20+
*/
21+
22+
namespace Mageplaza\DDoSProtect\Helper;
23+
24+
use DateTime;
25+
use Magento\Framework\App\CacheInterface;
26+
use Magento\Framework\App\Config\ScopeConfigInterface;
27+
use Magento\Framework\App\ResourceConnection;
28+
use Magento\Store\Model\StoreManagerInterface;
29+
use Mageplaza\DDoSProtect\Model\Request;
30+
use Psr\Log\LoggerInterface;
31+
32+
/**
33+
* Class DDos
34+
* Mageplaza\DDoSProtect\Helper
35+
*/
36+
class DDos
37+
{
38+
const MAX_REQUESTS = 'ddos_protect/general/max_requests'; // Maximum number of requests allowed
39+
const TIME_WINDOW = 'ddos_protect/general/time_window'; // Time window in seconds
40+
const XML_PATH_WHITELIST = 'ddos_protect/general/whitelist';
41+
const PATH = 'ddos_protect/general/path';
42+
43+
/**
44+
* @var CacheInterface
45+
*/
46+
protected $cache;
47+
48+
/**
49+
* @var LoggerInterface
50+
*/
51+
protected $logger;
52+
53+
/**
54+
* @var StoreManagerInterface
55+
*/
56+
protected $storeManager;
57+
58+
/**
59+
* @var ResourceConnection
60+
*/
61+
protected $resource;
62+
63+
/**
64+
* @var ScopeConfigInterface
65+
*/
66+
protected $scopeConfig;
67+
68+
/**
69+
* CheckIP constructor.
70+
*
71+
* @param CacheInterface $cache
72+
* @param LoggerInterface $logger
73+
* @param StoreManagerInterface $storeManager
74+
* @param ResourceConnection $resource
75+
* @param ScopeConfigInterface $scopeConfig
76+
*/
77+
public function __construct(
78+
CacheInterface $cache,
79+
LoggerInterface $logger,
80+
StoreManagerInterface $storeManager,
81+
ResourceConnection $resource,
82+
ScopeConfigInterface $scopeConfig
83+
84+
) {
85+
$this->cache = $cache;
86+
$this->logger = $logger;
87+
$this->storeManager = $storeManager;
88+
$this->resource = $resource;
89+
$this->scopeConfig = $scopeConfig;
90+
}
91+
92+
/**
93+
* @return $this
94+
*/
95+
public function saveIpAttackToDB()
96+
{
97+
$ipAttacks = $this->getDataFromCache(Request::IP_ATTACK);
98+
$connection = $this->resource->getConnection();
99+
$tableName = $this->resource->getTableName('mageplaza_ddos_protect');
100+
101+
102+
$dataToSaveDB = [];
103+
foreach ($ipAttacks as $ip => $ipAttack) {
104+
$dataToSaveDB[$ip] = [
105+
'ip_address' => $ipAttack['ip_address'],
106+
'last_request_time' => $ipAttack['last_request_time']
107+
];
108+
}
109+
if (!empty($dataToSaveDB)) {
110+
$connection->insertMultiple(
111+
$tableName,
112+
$dataToSaveDB
113+
);
114+
}
115+
116+
return $this;
117+
}
118+
119+
/**
120+
* Append data to existing cache key
121+
*
122+
* @param string $clientIP
123+
*
124+
* @return void
125+
*/
126+
public function handleClientIp($clientIP)
127+
{
128+
$cacheKey = Request::CLIENT_IP_CACHE_KEY;
129+
$clientsInfo = $this->getDataFromCache($cacheKey);
130+
if (!empty($clientsInfo)) {
131+
foreach ($clientsInfo as $ip => $clientData) {
132+
if ($clientIP === $ip) {
133+
if ($this->isDDoSAttack($clientIP, $clientsInfo)) {
134+
$this->appendAttackIpToCache($clientIP, $clientData);
135+
}
136+
break;
137+
} else {
138+
139+
$clientsInfo[$clientIP] = [
140+
'request_count' => 1,
141+
'last_request_time' => (new DateTime())->format('Y-m-d H:i:s')
142+
];
143+
144+
}
145+
}
146+
} else {
147+
//for none cache
148+
$clientsInfo = [
149+
$clientIP => [
150+
'request_count' => 1,
151+
'last_request_time' => (new DateTime())->format('Y-m-d H:i:s')
152+
]
153+
];
154+
}
155+
156+
$this->saveDataToCache($clientsInfo, $cacheKey);
157+
158+
}
159+
160+
161+
/**
162+
* Check if the request is part of a DDoS attack
163+
*
164+
* @param string $ipAddress
165+
* @param array $clientsInfo
166+
*
167+
* @return bool
168+
*/
169+
protected function isDDoSAttack($ipAddress, &$clientsInfo)
170+
{
171+
// Check if the IP is in the whitelist
172+
$whitelist = $this->getWhitelist();
173+
if (in_array($ipAddress, $whitelist)) {
174+
return false;
175+
}
176+
177+
// Calculate the time 15 minutes ago
178+
179+
$item_time = new DateTime($clientsInfo[$ipAddress]['last_request_time']);
180+
$requestCount = $clientsInfo[$ipAddress]['request_count'];
181+
if ((time() - $item_time->getTimestamp()) <= $this->getTimeWindow()) {
182+
if ($requestCount >= $this->getMaxRequests()) {
183+
return true;
184+
} else {
185+
$clientData['request_count'] = $clientsInfo[$ipAddress]['request_count']++;
186+
$clientData['ip_address'] = $ipAddress;
187+
$clientData['last_request_time'] = (new DateTime())->format('Y-m-d H:i:s');
188+
}
189+
} else {
190+
// Reset Count Request after more time have no request 60 second | self::TIME_WINDOW
191+
$clientsInfo = [
192+
'request_count' => 1,
193+
'ip_address' => $ipAddress,
194+
'last_request_time' => (new DateTime())->format('Y-m-d H:i:s')
195+
];
196+
}
197+
198+
return false;
199+
}
200+
201+
202+
/**
203+
* Get data from cache
204+
*
205+
* @param string $cacheKey
206+
*
207+
* @return array|string|null
208+
*/
209+
public function getDataFromCache($cacheKey, $withArr = true)
210+
{
211+
$cachedData = $this->cache->load($cacheKey);
212+
213+
if ($cachedData) {
214+
return $withArr ? json_decode($cachedData, true) : $cachedData;
215+
} else {
216+
return null;
217+
}
218+
}
219+
220+
/**
221+
* Append data to existing cache key
222+
*
223+
* @param string $attackIP
224+
* @param array $clientInfo
225+
*
226+
* @return void
227+
*/
228+
public function appendAttackIpToCache($attackIP, $clientInfo)
229+
{
230+
$clientsInfoCache = $this->getDataFromCache(Request::IP_ATTACK) ?: [];
231+
$clientsInfoCache[$attackIP] = $clientInfo;
232+
$this->saveDataToCache($clientsInfoCache, Request::IP_ATTACK);
233+
}
234+
235+
/**
236+
* @return array
237+
*/
238+
public function loadIpAttackCache()
239+
{
240+
$ipAttacksFromCache = $this->getDataFromCache(Request::IP_ATTACK);
241+
if (!$ipAttacksFromCache) {
242+
$ipAttacksFromCache = $this->getIpAttacksFormDB();
243+
$this->saveDataToCache($ipAttacksFromCache, Request::IP_ATTACK);
244+
}
245+
$whitelist = $this->getWhitelist();
246+
if (!empty($whitelist)) {
247+
foreach ($ipAttacksFromCache as $key => $ipAttack) {
248+
if (in_array($ipAttack, $whitelist)) {
249+
unset($ipAttacksFromCache[$key]);
250+
}
251+
}
252+
}
253+
254+
return $ipAttacksFromCache;
255+
}
256+
257+
/**
258+
* @param array $data
259+
* @param string $cacheKey
260+
*/
261+
public function saveDataToCache($data, $cacheKey)
262+
{
263+
$serializedData = json_encode($data);
264+
$this->cache->save($serializedData, $cacheKey);
265+
}
266+
267+
/**
268+
* @return array
269+
*/
270+
public function getIpAttacksFormDB()
271+
{
272+
$connection = $this->resource->getConnection();
273+
$select = $connection->select()
274+
->from('mageplaza_ddos_protect', ['ip_address', 'last_request_time']);
275+
$ipAddress = $connection->fetchAll($select);
276+
$ipAttack = [];
277+
foreach ($ipAddress as $ip) {
278+
$ipAttack[$ip['ip_address']] = [
279+
'ip_address' => $ip['ip_address'],
280+
'last_request_time' => $ip['last_request_time'],
281+
];
282+
}
283+
284+
return $ipAttack;
285+
}
286+
287+
/**
288+
* Get the IP whitelist from configuration
289+
*
290+
* @return array
291+
*/
292+
protected function getWhitelist()
293+
{
294+
295+
$whitelist = $this->scopeConfig->getValue(self::XML_PATH_WHITELIST);
296+
297+
return $whitelist ? array_map('trim', explode(',', $whitelist)) : [];
298+
}
299+
300+
/**
301+
* @return string
302+
*/
303+
public function getPath()
304+
{
305+
return $this->scopeConfig->getValue(self::PATH);
306+
}
307+
308+
/**
309+
* @return int
310+
*/
311+
protected function getTimeWindow()
312+
{
313+
return (int) $this->scopeConfig->getValue(self::TIME_WINDOW);
314+
}
315+
316+
/**
317+
* @return int
318+
*/
319+
protected function getMaxRequests()
320+
{
321+
return (int) $this->scopeConfig->getValue(self::MAX_REQUESTS);
322+
}
323+
}

etc/frontend/di.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
3+
<type name="Magento\Framework\App\FrontControllerInterface">
4+
<plugin name="ddos_protect_front_controller_plugin" type="Mageplaza\DDoSProtect\Plugin\FrontControllerPlugin"/>
5+
</type>
6+
</config>

0 commit comments

Comments
 (0)