PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.5.1
Defender Security – Malware Scanner, Login Security & Firewall v5.5.1
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.5.1, at src/controller/class-firewall-logs.php

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