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

883 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 === (int) $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, 'date', '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 Status', 'defender-security' ),
258 );
259 fputcsv( $fp, $headers, ',', '"', '\\' );
260
261 $flush_limit = Lockout_Log::INFINITE_SCROLL_SIZE;
262 foreach ( $logs as $key => $log ) {
263 $item = array(
264 $log->log,
265 $this->format_date_time( $log->date ),
266 $tl_component->get_type( $log->type ),
267 $log->ip,
268 $tl_component->get_ip_status_text( $log->ip ),
269 $ua_component->get_status_text( $log->type, $log->tried ),
270 );
271 fputcsv( $fp, $item, ',', '"', '\\' );
272
273 if ( 0 === $key % $flush_limit ) {
274 ob_flush();
275 flush();
276 }
277 }
278
279 fclose( $fp ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
280 exit();
281 }
282
283 /**
284 * Toggles an IP address to or from a specified list.
285 *
286 * @param Request $request The HTTP request object.
287 *
288 * @return Response The HTTP response object.
289 * @defender_route
290 */
291 public function toggle_ip_to_list( Request $request ): Response {
292 $data = $request->get_data(
293 array(
294 'ip' => array(
295 'type' => 'string',
296 'sanitize' => 'sanitize_text_field',
297 ),
298 'list' => array(
299 'type' => 'string',
300 'sanitize' => 'sanitize_text_field',
301 ),
302 'ban_status' => array(
303 'type' => 'string',
304 'sanitize' => 'sanitize_text_field',
305 ),
306 )
307 );
308
309 $ip = $data['ip'];
310 $collection = $data['list'];
311
312 $model = wd_di()->get( Blacklist_Lockout::class );
313 if ( $model->is_ip_in_list( $ip, $collection ) ) {
314 $model->remove_from_list( $ip, $collection );
315 /* translators: 1: IP address, 2: IP address list, 3: IP address list, 4: URL for Defender > Firewall > IP Banning. */
316 $message = esc_html__(
317 'IP %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
318 'defender-security'
319 );
320 } else {
321 $model->add_to_list( $ip, $collection );
322
323 $global_ip_service = wd_di()->get( Global_IP::class );
324 if ( $global_ip_service->can_blocklist_autosync() ) {
325 $global_ip_data = array(
326 'block_list' => array( $ip ),
327 );
328 $global_ip_service->add_to_global_ip_list( $global_ip_data );
329 }
330
331 /* translators: 1: IP address. 2: IP address list. 3: IP address list. 4: URL for Defender > Firewall > IP Banning. */
332 $message = esc_html__(
333 'IP %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
334 'defender-security'
335 );
336 }
337 $filter_data = $request->get_data(
338 array(
339 'date_from' => array(
340 'type' => 'string',
341 'sanitize' => 'sanitize_text_field',
342 ),
343 'date_to' => array(
344 'type' => 'string',
345 'sanitize' => 'sanitize_text_field',
346 ),
347 'ip_filter' => array(
348 'type' => 'string',
349 'sanitize' => 'sanitize_text_field',
350 ),
351 'type' => array(
352 'type' => 'string',
353 'sanitize' => 'sanitize_text_field',
354 ),
355 'paged' => array(
356 'type' => 'int',
357 'sanitize' => 'sanitize_text_field',
358 ),
359 'per_page' => array(
360 'type' => 'int',
361 'sanitize' => 'sanitize_text_field',
362 ),
363 )
364 );
365 $logs = Lockout_Log::get_logs_and_format(
366 array(
367 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
368 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
369 'ip' => $filter_data['ip_filter'],
370 // If this is all, then we set to null to exclude it from the filter.
371 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
372 ),
373 $filter_data['paged'],
374 'id',
375 'desc',
376 $filter_data['per_page']
377 );
378
379 return new Response(
380 true,
381 array(
382 'message' => sprintf(
383 $message,
384 $data['ip'] ?? '-',
385 $data['list'],
386 $data['list'],
387 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist' ) . '">' . esc_html__( 'IP Lockouts', 'defender-security' ) . '</a>'
388 ),
389 'logs' => $logs,
390 )
391 );
392 }
393
394 /**
395 * Toggles a user agent to/from a specified list based on the given request data.
396 *
397 * @param Request $request The request object containing the data for toggling the user agent.
398 *
399 * @return Response The response object indicating the success or failure of the toggle operation.
400 * @defender_route
401 */
402 public function toggle_ua_to_list( Request $request ): Response {
403 $data = $request->get_data(
404 array(
405 'ua' => array(
406 'type' => 'string',
407 'sanitize' => 'sanitize_text_field',
408 ),
409 'list' => array(
410 'type' => 'string',
411 'sanitize' => 'sanitize_text_field',
412 ),
413 'scenario' => array(
414 'type' => 'string',
415 'sanitize' => 'sanitize_text_field',
416 ),
417 )
418 );
419
420 $ua = $data['ua'];
421 $collection = $data['list'];
422 $action = $data['scenario'];
423
424 $model = wd_di()->get( User_Agent_Lockout::class );
425
426 if ( 'remove' === $action && $model->is_ua_in_list( $ua, $collection ) ) {
427 $model->remove_from_list( $ua, $collection );
428 /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
429 $message = esc_html__(
430 'User agent %1$s has been removed from your %2$s. You can control your %3$s in %4$s.',
431 'defender-security'
432 );
433 } elseif ( 'add' === $action ) {
434
435 /**
436 * 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`.
437 */
438 if ( 'blocklist' === $collection && $model->is_ua_in_list( $ua, 'allowlist' ) ) {
439 $model->remove_from_list( $ua, 'allowlist' );
440 }
441
442 if ( ! $model->is_ua_in_list( $ua, $collection ) ) {
443 $model->add_to_list( $ua, $collection );
444 }
445 /* translators: 1: User agent. 2: User agent list. 3: User agent list. 4: URL for Defender > Firewall > User Agent Banning. */
446 $message = esc_html__(
447 'User agent %1$s has been added to your %2$s. You can control your %3$s in %4$s.',
448 'defender-security'
449 );
450 } else {
451 return new Response(
452 false,
453 array( 'message' => esc_html__( 'Wrong result.', 'defender-security' ) )
454 );
455 }
456
457 $filter_data = $request->get_data(
458 array(
459 'date_from' => array(
460 'type' => 'string',
461 'sanitize' => 'sanitize_text_field',
462 ),
463 'date_to' => array(
464 'type' => 'string',
465 'sanitize' => 'sanitize_text_field',
466 ),
467 'ip_filter' => array(
468 'type' => 'string',
469 'sanitize' => 'sanitize_text_field',
470 ),
471 'type' => array(
472 'type' => 'string',
473 'sanitize' => 'sanitize_text_field',
474 ),
475 'paged' => array(
476 'type' => 'int',
477 'sanitize' => 'sanitize_text_field',
478 ),
479 'per_page' => array(
480 'type' => 'int',
481 'sanitize' => 'sanitize_text_field',
482 ),
483 )
484 );
485 $logs = Lockout_Log::get_logs_and_format(
486 array(
487 'from' => strtotime( $filter_data['date_from'] . ' 00:00:00' ),
488 'to' => strtotime( $filter_data['date_to'] . ' 23:59:59' ),
489 'ip' => $filter_data['ip_filter'],
490 // If this is all, then we set to null to exclude it from the filter.
491 'type' => 'all' === $filter_data['type'] ? '' : $filter_data['type'],
492 ),
493 $filter_data['paged'],
494 'id',
495 'desc',
496 $filter_data['per_page']
497 );
498
499 return new Response(
500 true,
501 array(
502 'message' => sprintf(
503 $message,
504 '<strong>' . $data['ua'] . '</strong>',
505 $data['list'],
506 $data['list'],
507 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=ua-lockout' ) . '">' . esc_html__( 'User Agent Banning', 'defender-security' ) . '</a>'
508 ),
509 'logs' => $logs,
510 )
511 );
512 }
513
514 /**
515 * Query the logs and display on frontend.
516 *
517 * @param Request $request The request object containing filter parameters.
518 *
519 * @return Response
520 * @defender_route
521 * @throws Exception If an argument is not of the expected type.
522 */
523 public function query_logs( Request $request ): Response {
524 $data = $request->get_data(
525 array(
526 'date_from' => array(
527 'type' => 'string',
528 'sanitize' => 'sanitize_text_field',
529 ),
530 'date_to' => array(
531 'type' => 'string',
532 'sanitize' => 'sanitize_text_field',
533 ),
534 'ip' => array(
535 'type' => 'string',
536 'sanitize' => 'sanitize_text_field',
537 ),
538 'type' => array(
539 'type' => 'string',
540 'sanitize' => 'sanitize_text_field',
541 ),
542 'paged' => array(
543 'type' => 'int',
544 'sanitize' => 'sanitize_text_field',
545 ),
546 'sort' => array(
547 'type' => 'string',
548 'sanitize' => 'sanitize_text_field',
549 ),
550 'ban_status' => array(
551 'type' => 'string',
552 'sanitize' => 'sanitize_text_field',
553 ),
554 )
555 );
556 // Validate.
557 $v = new Validator( $data, array() );
558 $v->rule( 'required', array( 'date_from', 'date_to' ) );
559 $v->rule( 'date', array( 'date_from', 'date_to' ) );
560 if ( ! $v->validate() ) {
561 return new Response( false, array( 'message' => esc_html__( 'Wrong start and end date.', 'defender-security' ) ) );
562 }
563 $sort = $data['sort'] ?? Table_Lockout::SORT_DESC;
564 switch ( $sort ) {
565 case 'ip':
566 $order = 'desc';
567 $order_by = 'ip';
568 break;
569 case 'oldest':
570 $order = 'asc';
571 $order_by = 'id';
572 break;
573 case 'user_agent':
574 $order = 'asc';
575 $order_by = 'user_agent';
576 break;
577 default:
578 $order = 'desc';
579 $order_by = 'id';
580 break;
581 }
582 // Convert date using timezone.
583 $timezone = wp_timezone();
584 $date_from = ( new DateTime( $data['date_from'], $timezone ) )
585 ->setTime( 0, 0, 0 )
586 ->getTimestamp();
587 $date_to = ( new DateTime( $data['date_to'], $timezone ) )
588 ->setTime( 23, 59, 59 )
589 ->getTimestamp();
590
591 $result = $this->retrieve_logs(
592 array(
593 'from' => $date_from,
594 'to' => $date_to,
595 'ip' => $data['ip'],
596 // If this is all, then we set to null to exclude it from the filter.
597 'type' => 'all' === $data['type'] ? '' : $data['type'],
598 'ban_status' => 'all' === $data['ban_status'] ? '' : $data['ban_status'],
599 ),
600 $data['paged'],
601 $order,
602 $order_by
603 );
604
605 return new Response( true, $result );
606 }
607
608 /**
609 * Enqueues scripts and styles for this page.
610 * Only enqueues assets if the page is active.
611 */
612 public function enqueue_assets() {
613 if ( ! $this->is_page_active() ) {
614 return;
615 }
616 wp_enqueue_script( 'def-momentjs', defender_asset_url( '/assets/js/vendor/moment/moment.min.js' ), array(), DEFENDER_VERSION, true );
617 wp_enqueue_script(
618 'def-daterangepicker',
619 defender_asset_url( '/assets/js/vendor/daterangepicker/daterangepicker.js' ),
620 array(),
621 DEFENDER_VERSION,
622 true
623 );
624 wp_localize_script(
625 'def-iplockout',
626 'lockout_logs',
627 array_merge( $this->data_frontend(), $this->dump_routes_and_nonces() )
628 );
629 }
630
631 /**
632 * Provides data for the frontend.
633 *
634 * @return array An array of data for the frontend.
635 */
636 public function data_frontend(): array {
637 $def_filters = array( 'misc' => wd_di()->get( Table_Lockout::class )->get_filters() );
638 $init_filters = array(
639 'from' => strtotime( '-30 days' ),
640 'to' => time(),
641 'type' => '',
642 'ip' => '',
643 'ban_status' => '',
644 );
645
646 return array_merge( $this->retrieve_logs( $init_filters, 1 ), $def_filters );
647 }
648
649 /**
650 * Retrieves logs based on the given filters, paging, order, and order by.
651 *
652 * @param array $filters An array containing the following keys:
653 * - 'from': The start date of the logs.
654 * - 'to': The end date of the logs.
655 * - 'type': The type of logs.
656 * - 'ip': The IP address of the logs.
657 * - 'ban_status': The ban status of the logs.
658 * @param int $paged The page number of the logs to retrieve. Default is 1.
659 * @param string $order The order of the logs. Default is 'desc'.
660 * @param string $order_by The field to order the logs by. Default is 'id'.
661 *
662 * @return array An array containing the following keys:
663 * - 'count': The total count of logs.
664 * - 'logs': The retrieved logs.
665 * - 'per_page': The number of logs per page.
666 * - 'total_pages': The total number of pages.
667 */
668 private function retrieve_logs( $filters, $paged = 1, $order = 'desc', $order_by = 'id' ): array {
669 // User can set the number of logs to retrieve per page.
670 $per_page = (int) defender_get_data_from_request( 'per_page', 'p' );
671 if ( 0 === $per_page ) {
672 $per_page = 20;
673 }
674 $conditions = array( 'ban_status' => $filters['ban_status'] );
675
676 $count = Lockout_Log::count( $filters['from'], $filters['to'], $filters['type'], $filters['ip'], $conditions );
677 $logs = Lockout_Log::get_logs_and_format( $filters, $paged, $order_by, $order, $per_page );
678
679 if ( - 1 === $per_page ) {
680 $per_page = Lockout_Log::INFINITE_SCROLL_SIZE;
681 }
682
683 return array(
684 'count' => $count,
685 'logs' => $logs,
686 'per_page' => $per_page,
687 'total_pages' => ceil( $count / $per_page ),
688 );
689 }
690
691 /**
692 * Converts the current object state to an array.
693 *
694 * @return array The array representation of the object.
695 */
696 public function to_array(): array {
697 return array();
698 }
699
700 /**
701 * Imports data into the model.
702 *
703 * @param array $data Data to be imported into the model.
704 */
705 public function import_data( array $data ) {
706 }
707
708 /**
709 * Removes settings for all submodules.
710 */
711 public function remove_settings() {
712 }
713
714 /**
715 * Delete all the data & the cache.
716 */
717 public function remove_data() {
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 */
734 private function maybe_send_reports( array $logs ): void {
735 $offset = 0;
736 $length = 1000;
737 $logs_chunk = array_slice( $logs, $offset, $length );
738 while ( ! empty( $logs_chunk ) ) {
739 $data = array(
740 'logs' => $logs_chunk,
741 );
742
743 $response = $this->antibot_client->send_reports( $data );
744
745 if ( is_wp_error( $response ) ) {
746 $this->log(
747 sprintf( 'AntiBot Global Firewall Error: %s', $response->get_error_message() ),
748 Firewall::FIREWALL_LOG
749 );
750 } elseif ( isset( $response['status'] ) && 'error' === $response['status'] ) {
751 $this->log(
752 sprintf( 'AntiBot Global Firewall Error: %s', $response['message'] ),
753 Firewall::FIREWALL_LOG
754 );
755 }
756
757 $offset += $length;
758 $logs_chunk = array_slice( $logs, $offset, $length );
759 }
760
761 $this->log( 'AntiBot Global Firewall: Process for sending logs completed.', Firewall::FIREWALL_LOG );
762 }
763
764 /**
765 * Send last 12 hours logs to AntiBot Global Firewall API.
766 * If running for first time then grab 7 days of logs.
767 * If last run difference is greater than 12 hours then grab 12+ hours of log but at most grab 7 days of logs.
768 *
769 * @return void
770 */
771 public function send_compact_logs_to_api(): void {
772 $site_id = get_current_blog_id();
773 $event_name = 'wpdef_firewall_send_compact_logs_to_api';
774 $this->log( "Cron job {$event_name} triggered at site {$site_id}", Firewall::FIREWALL_LOG );
775
776 /**
777 * Enable/disable sending Firewall logs to API.
778 *
779 * @param bool $status Status for sending logs. Send logs to API if true.
780 *
781 * @since 4.5.0
782 */
783 $send_logs = (bool) apply_filters( 'wpdef_firewall_send_logs_to_api', true );
784
785 if (
786 ! $send_logs ||
787 ! $this->wpmudev->is_dash_activated() ||
788 ! $this->wpmudev->is_site_connected_to_hub()
789 ) {
790 return;
791 }
792
793 // Acquire lock before executing.
794 if ( ! $this->acquire_cron_lock( $event_name, 'twicedaily' ) ) {
795 $this->log( "{$event_name} is skipped running from site {$site_id}", Firewall::FIREWALL_LOG );
796 return;
797 }
798 // Log the site ID where the event is triggered.
799 $this->log( "{$event_name} is processing from site {$site_id}", Firewall::FIREWALL_LOG );
800 $from = time() - ( 7 * DAY_IN_SECONDS );
801
802 $last_run_time = get_site_option( 'wpdef_ip_blocklist_sync_last_run_time' );
803 if ( $last_run_time ) {
804 $time_difference = time() - $last_run_time;
805
806 if ( $time_difference < 7 * DAY_IN_SECONDS ) { // 7 days in seconds
807 $from = $last_run_time;
808 }
809 }
810 update_site_option( 'wpdef_ip_blocklist_sync_last_run_time', time() );
811
812 $service = wd_di()->get( Firewall_Logs_Component::class );
813 $logs = $service->get_compact_logs( $from );
814
815 if ( ! empty( $logs ) ) {
816 $this->maybe_send_reports( $logs );
817 }
818
819 $logs = $service->get_akismet_auto_spam_comment_logs();
820 if ( ! empty( $logs ) ) {
821 $this->maybe_send_reports( $logs );
822 }
823
824 // Release lock after execution.
825 $this->release_cron_lock( $event_name );
826 }
827
828 /**
829 * Filters a successful HTTP API response before returning it.
830 *
831 * @param array $response HTTP response.
832 * @param array $parsed_args HTTP request arguments.
833 * @param string $url The request URL.
834 *
835 * @return array HTTP response.
836 */
837 public function akismet_http_response( $response, $parsed_args, $url ) {
838 // If the URL is not the Akismet comment-check endpoint, return the response as is.
839 if ( 'https://rest.akismet.com/1.1/comment-check' !== $url ) {
840 return $response;
841 }
842
843 // Retrieve response body safely.
844 $body = wp_remote_retrieve_body( $response );
845 // If the body is empty or does not equal 'true' (indicating spam), return the response as is.
846 if ( empty( $body ) || trim( $body ) !== 'true' ) {
847 return $response;
848 }
849
850 // Ensure the request body contains data; otherwise, return the response.
851 if ( empty( $parsed_args['body'] ) ) {
852 return $response;
853 }
854
855 $request_data = wp_parse_args( $parsed_args['body'] );
856 // If the comment author's IP is not present in the request data, return the response.
857 if ( empty( $request_data['comment_author_IP'] ) ) {
858 return $response;
859 }
860
861 // Validate the user IP address from the request data.
862 $user_ip = filter_var( $request_data['comment_author_IP'], FILTER_VALIDATE_IP );
863 if ( false === $user_ip ) {
864 return $response;
865 }
866
867 // Retrieve the current list of blocked IPs from the site transient.
868 $option = get_site_transient( self::AKISMET_BLOCKED_IPS );
869 // Ensure the retrieved data is an array; if not, initialize it as an empty array.
870 if ( ! is_array( $option ) ) {
871 $option = array();
872 }
873
874 // Increment the count of how many times this IP has been associated with spam.
875 $option[ $user_ip ] = isset( $option[ $user_ip ] ) ? (int) $option[ $user_ip ] + 1 : 1;
876 // Update the site transient with the new list of blocked IPs.
877 set_site_transient( self::AKISMET_BLOCKED_IPS, $option );
878
879 // Return the original HTTP response.
880 return $response;
881 }
882 }
883