PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
6.3.0 6.2.3 6.2.4 6.2.0 6.2.1 6.2.2 6.1.0 5.3.1 5.4.0 5.4.1 5.5.0 5.5.1 5.6.0 5.6.1 5.6.2 5.7.0 5.7.1 5.7.2 5.8.0 5.8.1 5.9.0 6.0.0 6.0.1 3.0.1 3.1.0 All 141 releases
defender-security / src / controller / class-firewall-logs.php

class-firewall-logs.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.2, at src/controller/class-firewall-logs.php

893 lines 26.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles firewall logs and interactions with Block list API service.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use DateTime;
11 use Exception;
12 use Countable;
13 use Valitron\Validator;
14 use Calotes\Helper\HTTP;
15 use WP_Defender\Controller;
16 use Calotes\Component\Request;
17 use Calotes\Component\Response;
18 use WP_Defender\Traits\Formats;
19 use WP_Defender\Behavior\WPMUDEV;
20 use WP_Defender\Model\Lockout_Log;
21 use WP_Defender\Component\User_Agent;
22 use WP_Defender\Component\IP\Global_IP;
23 use WP_Defender\Component\Table_Lockout;
24 use WP_Defender\Component\Network_Cron_Manager;
25 use WP_Defender\Integrations\Antibot_Global_Firewall_Client;
26 use WP_Defender\Model\Setting\Blacklist_Lockout;
27 use WP_Defender\Model\Setting\User_Agent_Lockout;
28 use WP_Defender\Component\Firewall_Logs as Firewall_Logs_Component;
29
30 /**
31 * Responsible for managing firewall logs, including bulk actions, exporting logs to CSV,
32 * toggling IP addresses and user agents, querying logs, and sending logs to the Block list API.
33 */
34 class Firewall_Logs extends Controller {
35
36 use Formats;
37
38 /**
39 * The slug identifier for this controller.
40 *
41 * @var string
42 */
43 protected $slug = 'wdf-ip-lockout';
44
45 /**
46 * The WPMUDEV instance used for interacting with WPMUDEV services.
47 *
48 * @var WPMUDEV
49 */
50 private $wpmudev;
51
52 /**
53 * The client for interacting with the AntiBot Global Firewall API.
54 *
55 * @var Antibot_Global_Firewall_Client
56 */
57 private $antibot_client;
58
59 /**
60 * The transient key used to store the list of IP addresses from the
61 * Akismet service that are blocked in the firewall.
62 *
63 * @var string
64 */
65 const AKISMET_BLOCKED_IPS = 'defender_akismet_blocked_ips';
66
67 /**
68 * Constructor for the class.
69 *
70 * @param Antibot_Global_Firewall_Client $antibot_client The client for interacting with the Block list API service.
71 */
72 public function __construct( Antibot_Global_Firewall_Client $antibot_client ) {
73 $this->register_routes();
74 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
75
76 $this->wpmudev = wd_di()->get( WPMUDEV::class );
77
78 $this->antibot_client = $antibot_client;
79
80 /**
81 * Send Firewall logs to AntiBot Global Firewall API.
82 *
83 * @var Network_Cron_Manager $network_cron_manager
84 */
85 $network_cron_manager = wd_di()->get( Network_Cron_Manager::class );
86 $network_cron_manager->register_callback(
87 'wpdef_firewall_send_compact_logs_to_api',
88 array( $this, 'send_compact_logs_to_api' ),
89 12 * HOUR_IN_SECONDS,
90 time() + 15
91 );
92 if ( class_exists( 'Akismet' ) ) {
93 add_filter( 'http_response', array( $this, 'akismet_http_response' ), 10, 3 );
94 }
95 }
96
97 /**
98 * Bulk action handler for lockout logs.
99 *
100 * @param Request $request The request object containing the data.
101 *
102 * @return Response The response object with the result of the bulk action.
103 * @defender_route
104 */
105 public function bulk( Request $request ) {
106 $data = $request->get_data(
107 array(
108 'action' => array(
109 'type' => 'string',
110 'sanitize' => 'sanitize_text_field',
111 ),
112 'ids' => array(
113 'type' => 'array',
114 ),
115 )
116 );
117 $ids = $data['ids'];
118 $ips = array();
119 $logs = array();
120 if ( is_array( $ids ) || $ids instanceof Countable ? count( $ids ) : 0 ) {
121 foreach ( $ids as $id ) {
122 $model = Lockout_Log::find_by_id( $id );
123 if ( is_object( $model ) ) {
124 $bl = wd_di()->get( Blacklist_Lockout::class );
125 switch ( $data['action'] ) {
126 case 'ban':
127 $bl->remove_from_list( $model->ip, 'allowlist' );
128 $bl->add_to_list( $model->ip, 'blocklist' );
129 $ips[ $model->ip ] = $model->ip;
130 $logs[] = $model;
131 break;
132 case 'allowlist':
133 $bl->remove_from_list( $model->ip, 'blocklist' );
134 $bl->add_to_list( $model->ip, 'allowlist' );
135 $ips[ $model->ip ] = $model->ip;
136 $logs[] = $model;
137 break;
138 case 'delete':
139 $ips[ $model->ip ] = $model->ip;
140 $model->delete();
141 break;
142 default:
143 break;
144 }
145 }
146 }
147 }
148
149 if ( count( $logs ) > 0 ) {
150 $logs = Lockout_Log::format_logs( $logs );
151 }
152
153 switch ( $data['action'] ) {
154 case 'allowlist':
155 $messages = sprintf(
156 /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
157 esc_html__(
158 'IP %1$s has been added to your allowlist. You can control your allowlist in %2$s.',
159 'defender-security'
160 ),
161 implode( ', ', $ips ),
162 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist#tab-ip-allowlist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
163 );
164 break;
165 case 'ban':
166 $messages = sprintf(
167 /* translators: 1: IP Address(es). 2: URL for Defender > Firewall > IP Banning. */
168 esc_html__(
169 'IP %1$s has been added to your blocklist. You can control your blocklist in %2$s.',
170 'defender-security'
171 ),
172 implode( ', ', $ips ),
173 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
174 );
175 break;
176 case 'delete':
177 $messages = sprintf(
178 /* translators: %s: IP Address(es) */
179 esc_html__( 'IP %s has been deleted', 'defender-security' ),
180 implode( ', ', $ips )
181 );
182 break;
183 default:
184 $messages = '';
185 break;
186 }
187
188 return new Response(
189 true,
190 array(
191 'message' => $messages,
192 'logs' => $logs,
193 )
194 );
195 }
196
197 /**
198 * Export logs to CSV
199 *
200 * @return void
201 * @defender_route
202 * @throws Exception On failure.
203 */
204 public function export_as_csv(): void {
205 $date_from = HTTP::get( 'date_from', strtotime( '-7 days midnight' ) );
206 $date_to = HTTP::get( 'date_to', strtotime( 'tomorrow' ) );
207 // Convert date using timezone.
208 $timezone = wp_timezone();
209 $date_from = ( new DateTime( $date_from, $timezone ) )->setTime( 0, 0, 0 )->getTimestamp();
210 $date_to = ( new DateTime( $date_to, $timezone ) )->setTime( 23, 59, 59 )->getTimestamp();
211 $filters = array(
212 'from' => $date_from,
213 'to' => $date_to,
214 'type' => HTTP::get( 'term', '' ),
215 'ip' => HTTP::get( 'ip', '' ),
216 'ban_status' => HTTP::get( 'ban_status', '' ),
217 );
218
219 if ( 'all' === $filters['type'] ) {
220 $filters['type'] = '';
221 }
222
223 if ( 'all' === $filters['ban_status'] ) {
224 $filters['ban_status'] = '';
225 }
226 // User can export the number of logs that are set.
227 $per_page = (int) defender_get_data_from_request( 'per_page', 'g' );
228 if ( 0 === $per_page ) {
229 $per_page = 20;
230 }
231 if ( - 1 === $per_page ) {
232 $per_page = false;
233 }
234
235 $paged = (int) defender_get_data_from_request( 'paged', 'g' );
236 if ( 0 === $paged ) {
237 $paged = 1;
238 }
239 $logs = Lockout_Log::query_logs( $filters, $paged, 'id', 'desc', $per_page );
240
241 $tl_component = new Table_Lockout();
242
243 $ua_component = wd_di()->get( User_Agent::class );
244
245 $filename = 'wdf-lockout-logs-export-' . wp_date( 'ymdHis' ) . '.csv';
246
247 header( 'Expires: 0' );
248 header( 'Cache-Control: must-revalidate, post-check=0, pre-check=0' );
249 header( 'Cache-Control: private', false );
250 header( 'Content-Type: application/octet-stream' );
251 header( 'Content-Disposition: attachment; filename="' . $filename . '";' );
252 header( 'Content-Transfer-Encoding: binary' );
253
254 extension_loaded( 'zlib' ) ? ob_start( 'ob_gzhandler' ) : ob_start();
255
256 $fp = fopen( 'php://output', 'w' );
257 $headers = array(
258 esc_html__( 'Log', 'defender-security' ),
259 esc_html__( 'Date / Time', 'defender-security' ),
260 esc_html__( 'Type', 'defender-security' ),
261 esc_html__( 'IP address', 'defender-security' ),
262 esc_html__( 'IP Status', 'defender-security' ),
263 esc_html__( 'User Agent Name', 'defender-security' ),
264 esc_html__( 'User Agent Status', 'defender-security' ),
265 );
266 fputcsv( $fp, $headers, ',', '"', '\\' );
267
268 $flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE;
269 foreach ( $logs as $key => $log ) {
270 $item = array(
271 $log->log,
272 $this->format_date_time( $log->date ),
273 $tl_component->get_type( $log->type ),
274 $log->ip,
275 $tl_component->get_ip_status_text( $log->ip ),
276 $log->user_agent,
277 $ua_component->get_status_text( $log->type, $log->tried ),
278 );
279 fputcsv( $fp, $item, ',', '"', '\\' );
280
281 if ( 0 === $key % $flush_limit ) {
282 ob_flush();
283 flush();
284 }
285 }
286 // WP_Filesystem is not suitable here because it abstracts to reading/writing files on disk, not to output streams.
287 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
288 exit();
289 }
290
291 /**
292 * Toggles an IP address to or from a specified list.
293 *
294 * @param Request $request The HTTP request object.
295 *
296 * @return Response The HTTP response object.
297 * @defender_route
298 */
299 public function toggle_ip_to_list( Request $request ): Response {
300 $data = $request->get_data(
301 array(
302 'ip' => array(
303 'type' => 'string',
304 'sanitize' => 'sanitize_text_field',
305 ),
306 'list' => array(
307 'type' => 'string',
308 'sanitize' => 'sanitize_text_field',
309 ),
310 'ban_status' => array(
311 'type' => 'string',
312 'sanitize' => 'sanitize_text_field',
313 ),
314 )
315 );
316
317 $ip = $data['ip'];
318 $collection = $data['list'];
319
320 $model = wd_di()->get( Blacklist_Lockout::class );
321 if ( $model->is_ip_in_list( $ip, $collection ) ) {
322 $model->remove_from_list( $ip, $collection );
323 /* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */
324 $message = esc_html__(
325 'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
326 'defender-security'
327 );
328 } else {
329 $model->add_to_list( $ip, $collection );
330
331 $global_ip_service = wd_di()->get( Global_IP::class );
332 if ( $global_ip_service->can_blocklist_autosync() ) {
333 $global_ip_data = array(
334 'block_list' => array( $ip ),
335 );
336 $global_ip_service->add_to_global_ip_list( $global_ip_data );
337 }
338
339 /* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */
340 $message = esc_html__(
341 'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
342 'defender-security'
343 );
344 }
345 $filter_data = $request->get_data(
346 array(
347 'date_from' => array(
348 'type' => 'string',
349 'sanitize' => 'sanitize_text_field',
350 ),
351 'date_to' => array(
352 'type' => 'string',
353 'sanitize' => 'sanitize_text_field',
354 ),
355 'ip_filter' => array(
356 'type' => 'string',
357 'sanitize' => 'sanitize_text_field',
358 ),
359 'type' => array(
360 'type' => 'string',
361 'sanitize' => 'sanitize_text_field',
362 ),
363 'paged' => array(
364 'type' => 'int',
365 'sanitize' => 'sanitize_text_field',
366 ),
367 'per_page' => array(
368 'type' => 'int',
369 'sanitize' => 'sanitize_text_field',
370 ),
371 )
372 );
373 $logs = Lockout_Log::get_logs_and_format(
374 array(
375 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
376 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
377 'ip' => $filter_data['ip_filter'],
378 // If this is all, then we set to null to exclude it from the filter.
379 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
380 ),
381 $filter_data['paged'],
382 'id',
383 'desc',
384 $filter_data['per_page']
385 );
386
387 return new Response(
388 true,
389 array(
390 'message' => sprintf(
391 $message,
392 $data['ip'] ?? '-',
393 $data['list'],
394 $data['list'],
395 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
396 ),
397 'logs' => $logs,
398 )
399 );
400 }
401
402 /**
403 * Toggles a user agent to/from a specified list based on the given request data.
404 *
405 * @param Request $request The request object containing the data for toggling the user agent.
406 *
407 * @return Response The response object indicating the success or failure of the toggle operation.
408 * @defender_route
409 */
410 public function toggle_ua_to_list( Request $request ): Response {
411 $data = $request->get_data(
412 array(
413 'ua' => array(
414 'type' => 'string',
415 'sanitize' => 'sanitize_text_field',
416 ),
417 'list' => array(
418 'type' => 'string',
419 'sanitize' => 'sanitize_text_field',
420 ),
421 'scenario' => array(
422 'type' => 'string',
423 'sanitize' => 'sanitize_text_field',
424 ),
425 )
426 );
427
428 $ua = $data['ua'];
429 $collection = $data['list'];
430 $action = $data['scenario'];
431
432 $model = wd_di()->get( User_Agent_Lockout::class );
433
434 if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) {
435 $model->remove_from_list( $ua, $collection );
436 /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
437 $message = esc_html__(
438 'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
439 'defender-security'
440 );
441 } elseif ( 'add' === $action ) {
442
443 /**
444 * Possible scenario on regex blocklist. For e.g. UA term `run` present in allowlist & `r.n` regex in blocklist then remove `run` to block `run` user agent using regex `r.n`.
445 */
446 if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) {
447 $model->remove_from_list( $ua, 'allowlist' );
448 }
449
450 if ( ! $model->is_ua_in_list( $ua, $collection ) ) {
451 $model->add_to_list( $ua, $collection );
452 }
453 /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
454 $message = esc_html__(
455 'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
456 'defender-security'
457 );
458 } else {
459 return new Response(
460 false,
461 array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) )
462 );
463 }
464
465 $filter_data = $request->get_data(
466 array(
467 'date_from' => array(
468 'type' => 'string',
469 'sanitize' => 'sanitize_text_field',
470 ),
471 'date_to' => array(
472 'type' => 'string',
473 'sanitize' => 'sanitize_text_field',
474 ),
475 'ip_filter' => array(
476 'type' => 'string',
477 'sanitize' => 'sanitize_text_field',
478 ),
479 'type' => array(
480 'type' => 'string',
481 'sanitize' => 'sanitize_text_field',
482 ),
483 'paged' => array(
484 'type' => 'int',
485 'sanitize' => 'sanitize_text_field',
486 ),
487 'per_page' => array(
488 'type' => 'int',
489 'sanitize' => 'sanitize_text_field',
490 ),
491 )
492 );
493 $logs = Lockout_Log::get_logs_and_format(
494 array(
495 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
496 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
497 'ip' => $filter_data['ip_filter'],
498 // If this is all, then we set to null to exclude it from the filter.
499 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
500 ),
501 $filter_data['paged'],
502 'id',
503 'desc',
504 $filter_data['per_page']
505 );
506
507 return new Response(
508 true,
509 array(
510 'message' => sprintf(
511 $message,
512 '<strong>' . $data['ua'] . '</strong>',
513 $data['list'],
514 $data['list'],
515 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>'
516 ),
517 'logs' => $logs,
518 // Include blocklist preset values for the frontend.
519 'blocklist_preset_values' => $model->blocklist_preset_values,
520 )
521 );
522 }
523
524 /**
525 * Query the logs and display on frontend.
526 *
527 * @param Request $request The request object containing filter parameters.
528 *
529 * @return Response
530 * @defender_route
531 * @throws Exception If an argument is not of the expected type.
532 */
533 public function query_logs( Request $request ): Response {
534 $data = $request->get_data(
535 array(
536 'date_from' => array(
537 'type' => 'string',
538 'sanitize' => 'sanitize_text_field',
539 ),
540 'date_to' => array(
541 'type' => 'string',
542 'sanitize' => 'sanitize_text_field',
543 ),
544 'ip' => array(
545 'type' => 'string',
546 'sanitize' => 'sanitize_text_field',
547 ),
548 'type' => array(
549 'type' => 'string',
550 'sanitize' => 'sanitize_text_field',
551 ),
552 'paged' => array(
553 'type' => 'int',
554 'sanitize' => 'sanitize_text_field',
555 ),
556 'sort' => array(
557 'type' => 'string',
558 'sanitize' => 'sanitize_text_field',
559 ),
560 'ban_status' => array(
561 'type' => 'string',
562 'sanitize' => 'sanitize_text_field',
563 ),
564 )
565 );
566 // Validate.
567 $v = new Validator( $data, array() );
568 $v->rule( 'required', array( 'date_from', 'date_to' ) );
569 $v->rule( 'date', array( 'date_from', 'date_to' ) );
570 if ( ! $v->validate() ) {
571 return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) );
572 }
573 $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
574 switch ( $sort ) {
575 case 'ip':
576 $order = 'desc';
577 $order_by = 'ip';
578 break;
579 case 'oldest':
580 $order = 'asc';
581 $order_by = 'id';
582 break;
583 case 'user_agent':
584 $order = 'asc';
585 $order_by = 'user_agent';
586 break;
587 default:
588 $order = 'desc';
589 $order_by = 'id';
590 break;
591 }
592 // Convert date using timezone.
593 $timezone = wp_timezone();
594 $date_from = ( new DateTime( $data['date_from'], $timezone ) )
595 ->setTime( 0, 0, 0 )
596 ->getTimestamp();
597 $date_to = ( new DateTime( $data['date_to'], $timezone ) )
598 ->setTime( 23, 59, 59 )
599 ->getTimestamp();
600
601 $result = $this->retrieve_logs(
602 array(
603 'from' => $date_from,
604 'to' => $date_to,
605 'ip' => $data['ip'],
606 // If this is all, then we set to null to exclude it from the filter.
607 'type' => 'all' === $data['type'] ? '' : $data['type'],
608 'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'],
609 ),
610 $data['paged'],
611 $order,
612 $order_by
613 );
614
615 return new Response( true, $result );
616 }
617
618 /**
619 * Enqueues scripts and styles for this page.
620 * Only enqueues assets if the page is active.
621 */
622 public function enqueue_assets() {
623 if ( ! $this->is_page_active() ) {
624 return;
625 }
626 wp_localize_script(
627 'def-iplockout',
628 'lockout_logs',
629 array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
630 );
631 }
632
633 /**
634 * Provides data for the frontend.
635 *
636 * @return array An array of data for the frontend.
637 */
638 public function data_frontend(): array {
639 $type = defender_get_data_from_request( 'type', 'g' );
640
641 $def_filters = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() );
642 $init_filters = array(
643 'from' => strtotime( '-30 days' ),
644 'to' => time(),
645 'type' => $type,
646 'ip' => '',
647 'ban_status' => '',
648 );
649
650 return array_merge( $this->retrieve_logs( $init_filters, 1 ), $def_filters );
651 }
652
653 /**
654 * Retrieves logs based on the given filters, paging, order, and order by.
655 *
656 * @param array $filters An array containing the following keys:
657 * - 'from': The start date of the logs.
658 * - 'to': The end date of the logs.
659 * - 'type': The type of logs.
660 * - 'ip': The IP address of the logs.
661 * - 'ban_status': The ban status of the logs.
662 * @param int $paged The page number of the logs to retrieve. Default is 1.
663 * @param string $order The order of the logs. Default is 'desc'.
664 * @param string $order_by The field to order the logs by. Default is 'id'.
665 *
666 * @return array An array containing the following keys:
667 * - 'count': The total count of logs.
668 * - 'logs': The retrieved logs.
669 * - 'per_page': The number of logs per page.
670 * - 'total_pages': The total number of pages.
671 */
672 private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array {
673 // User can set the number of logs to retrieve per page.
674 $per_page = (int) defender_get_data_from_request( 'per_page', 'p' );
675 if ( 0 === $per_page ) {
676 $per_page = 20;
677 }
678 $conditions = array( 'ban_status' => $filters['ban_status'] );
679
680 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
681 $logs = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page );
682 return array(
683 'count' => $count,
684 'logs' => $logs,
685 'per_page' => $per_page,
686 'total_pages' => ceil( $count / $per_page ),
687 );
688 }
689
690 /**
691 * Converts the current object state to an array.
692 *
693 * @return array The array representation of the object.
694 */
695 public function to_array(): array {
696 return array();
697 }
698
699 /**
700 * Imports data into the model.
701 *
702 * @param array $data Data to be imported into the model.
703 */
704 public function import_data( array $data ) {
705 }
706
707 /**
708 * Removes settings for all submodules.
709 */
710 public function remove_settings() {
711 }
712
713 /**
714 * Delete all the data & the cache.
715 */
716 public function remove_data() {
717 delete_site_transient( self::AKISMET_BLOCKED_IPS );
718 }
719
720 /**
721 * Exports strings.
722 *
723 * @return array An array of strings.
724 */
725 public function export_strings(): array {
726 return array();
727 }
728
729 /**
730 * Exports strings.
731 *
732 * @param array $logs Prepared logs.
733 * @param bool $is_staging Send logs to staging.
734 */
735 private function maybe_send_reports( array $logs, bool $is_staging = false ): void {
736 $offset = 0;
737 $length = 1000;
738 $logs_chunk = array_slice( $logs, $offset, $length );
739 while ( ! empty( $logs_chunk ) ) {
740 $data = array(
741 'logs' => $logs_chunk,
742 );
743
744 $antibot_client = $this->antibot_client;
745 if ( $is_staging ) {
746 $antibot_client = new Antibot_Global_Firewall_Client( 'https://staging-api.blocklist-service.com' );
747 }
748 $response = $antibot_client->send_reports( $data );
749
750 if ( is_wp_error( $response ) ) {
751 $this->log(
752 sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ),
753 Firewall::FIREWALL_LOG
754 );
755 } elseif ( isset( $response['status'] ) && 'error' === $response['status'] ) {
756 $this->log(
757 sprintf( 'AntiBot Global Firewall Error: %s', $response['message'] ),
758 Firewall::FIREWALL_LOG
759 );
760 }
761
762 $offset += $length;
763 $logs_chunk = array_slice( $logs, $offset, $length );
764 }
765
766 $this->log( 'AntiBot Global Firewall: Process for sending logs completed.', Firewall::FIREWALL_LOG );
767 }
768
769 /**
770 * Send last 12 hours logs to AntiBot Global Firewall API.
771 * If running for first time then grab 7 days of logs.
772 * If last run difference is greater than 12 hours then grab 12+ hours of log but at most grab 7 days of logs.
773 *
774 * @return void
775 */
776 public function send_compact_logs_to_api(): void {
777 $site_id = get_current_blog_id();
778 $event_name = 'wpdef_firewall_send_compact_logs_to_api';
779 $this->log( "Cron job {$event_name} triggered at site {$site_id}", Firewall::FIREWALL_LOG );
780
781 /**
782 * Enable/disable sending Firewall logs to API.
783 *
784 * @param bool $status Status for sending logs. Send logs to API if true.
785 *
786 * @since 4.5.0
787 */
788 $send_logs = (bool) apply_filters( 'wpdef_firewall_send_logs_to_api', true );
789
790 if (
791 ! $send_logs ||
792 ! $this->wpmudev->is_dash_activated() ||
793 ! $this->wpmudev->is_site_connected_to_hub()
794 ) {
795 return;
796 }
797
798 // Acquire lock before executing.
799 if ( ! $this->acquire_cron_lock( $event_name, 'twicedaily' ) ) {
800 $this->log( "{$event_name} is skipped running from site {$site_id}", Firewall::FIREWALL_LOG );
801 return;
802 }
803 // Log the site ID where the event is triggered.
804 $this->log( "{$event_name} is processing from site {$site_id}", Firewall::FIREWALL_LOG );
805 $from = time() - ( 7 * DAY_IN_SECONDS );
806
807 $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' );
808 if ( $last_run_time ) {
809 $time_difference = time() - $last_run_time;
810
811 if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds
812 $from = $last_run_time;
813 }
814 }
815 update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() );
816
817 $service = wd_di()->get( Firewall_Logs_Component::class );
818
819 $logs = $service->get_compact_logs( $from );
820 if ( ! empty( $logs ) ) {
821 $this->maybe_send_reports( $logs );
822 }
823
824 $logs = $service->get_akismet_auto_spam_comment_logs();
825 if ( ! empty( $logs ) ) {
826 $this->maybe_send_reports( $logs );
827 }
828
829 $logs = $service->get_404_intelligence_logs( $from );
830 if ( ! empty( $logs ) ) {
831 $this->maybe_send_reports( $logs, true );
832 }
833
834 // Release lock after execution.
835 $this->release_cron_lock( $event_name );
836 }
837
838 /**
839 * Filters a successful HTTP API response before returning it.
840 *
841 * @param array $response HTTP response.
842 * @param array $parsed_args HTTP request arguments.
843 * @param string $url The request URL.
844 *
845 * @return array HTTP response.
846 */
847 public function akismet_http_response( $response, $parsed_args, $url ) {
848 // If the URL is not the Akismet comment-check endpoint, return the response as is.
849 if ( 'https://rest.akismet.com/1.1/comment-check' !== $url ) {
850 return $response;
851 }
852
853 // Retrieve response body safely.
854 $body = wp_remote_retrieve_body( $response );
855 // If the body is empty or does not equal 'true' (indicating spam), return the response as is.
856 if ( empty( $body ) || trim( $body ) !== 'true' ) {
857 return $response;
858 }
859
860 // Ensure the request body contains data; otherwise, return the response.
861 if ( empty( $parsed_args['body'] ) ) {
862 return $response;
863 }
864
865 $request_data = wp_parse_args( $parsed_args['body'] );
866 // If the comment author's IP is not present in the request data, return the response.
867 if ( empty( $request_data['comment_author_IP'] ) ) {
868 return $response;
869 }
870
871 // Validate the user IP address from the request data.
872 $user_ip = filter_var( $request_data['comment_author_IP'], FILTER_VALIDATE_IP );
873 if ( false === $user_ip ) {
874 return $response;
875 }
876
877 // Retrieve the current list of blocked IPs from the site transient.
878 $option = get_site_transient( self::AKISMET_BLOCKED_IPS );
879 // Ensure the retrieved data is an array; if not, initialize it as an empty array.
880 if ( ! is_array( $option ) ) {
881 $option = array();
882 }
883
884 // Increment the count of how many times this IP has been associated with spam.
885 $option[ $user_ip ] = isset( $option[ $user_ip ] ) ? (int) $option[ $user_ip ] + 1 : 1;
886 // Update the site transient with the new list of blocked IPs.
887 set_site_transient( self::AKISMET_BLOCKED_IPS, $option );
888
889 // Return the original HTTP response.
890 return $response;
891 }
892 }
893