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-antibot-global-firewall.php

class-antibot-global-firewall.php in Defender Security – Malware Scanner, Login Security & Firewall 5.5.1, at src/controller/class-antibot-global-firewall.php

658 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * It handles functionalities such as saving settings and handle the request for downloading the IPs from AntiBot Global Firewall.
4 *
5 * @package WP_Defender\Controller
6 */
7
8 namespace WP_Defender\Controller;
9
10 use Calotes\Component\Request;
11 use Calotes\Component\Response;
12 use WP_Defender\Component\Config\Config_Hub_Helper;
13 use WP_Defender\Event;
14 use WP_Defender\Traits\Setting;
15 use WP_Defender\Traits\Defender_Dashboard_Client;
16 use WP_Defender\Model\Setting\Antibot_Global_Firewall_Setting;
17 use WP_Defender\Behavior\WPMUDEV;
18 use WP_Defender\Component\IP\Antibot_Global_Firewall as Antibot_Global_Firewall_Component;
19 use WP_Defender\Component\Blacklist_Lockout;
20 use WP_Defender\Component\IP\Global_IP;
21 use WP_Defender\Component\Scheduler\Scheduler;
22 use WP_Defender\Helper\Analytics\Antibot as Antibot_Analytics;
23
24 /**
25 * Class Antibot_Global_Firewall.
26 */
27 class Antibot_Global_Firewall extends Event {
28 use Setting;
29 use Defender_Dashboard_Client;
30
31 /**
32 * The slug identifier for this controller.
33 *
34 * @var string
35 */
36 protected $slug = 'wdf-ip-lockout';
37
38 /**
39 * The model for handling AntiBot Global Firewall settings.
40 *
41 * @var Antibot_Global_Firewall_Setting
42 */
43 protected $model;
44
45 /**
46 * The service for handling AntiBot Global Firewall logic.
47 *
48 * @var Antibot_Global_Firewall_Component
49 */
50 protected $service;
51
52 /**
53 * The WPMUDEV instance.
54 *
55 * @var WPMUDEV
56 */
57 private $wpmudev;
58
59 /**
60 * Initializes the class with model and service.
61 *
62 * @param Antibot_Global_Firewall_Setting $model The model for handling AntiBot Global Firewall settings.
63 * @param Antibot_Global_Firewall_Component $service The service for handling AntiBot Global Firewall logic.
64 * @param WPMUDEV $wpmudev The WPMUDEV instance.
65 */
66 public function __construct( Antibot_Global_Firewall_Setting $model, Antibot_Global_Firewall_Component $service, WPMUDEV $wpmudev ) {
67 $this->register_routes();
68 add_action( 'defender_enqueue_assets', array( $this, 'enqueue_assets' ) );
69
70 $this->model = $model;
71 $this->service = $service;
72 $this->wpmudev = $wpmudev;
73
74 add_action( 'wpmudev_hub_connector_first_sync_completed', array( $this, 'maybe_hcm_connection_attempt' ) );
75
76 /**
77 * Download and store Blocklist from the API.
78 */
79 if ( $this->service->is_active_via_plugin() ) {
80 if ( ! wp_next_scheduled( 'wpdef_antibot_global_firewall_fetch_blocklist' ) ) {
81 wp_schedule_event( time() + 15, Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_SCHEDULE, 'wpdef_antibot_global_firewall_fetch_blocklist' );
82 }
83 } elseif ( wp_next_scheduled( 'wpdef_antibot_global_firewall_fetch_blocklist' ) ) {
84 wp_clear_scheduled_hook( 'wpdef_antibot_global_firewall_fetch_blocklist' );
85 }
86 add_action( 'wpdef_antibot_global_firewall_fetch_blocklist', array( $this, 'handle_download_and_store_blocklist' ) );
87
88 if ( $this->wpmudev->is_wpmu_hosting() ) {
89 add_action( 'init', array( $this, 'sync_state' ) );
90 }
91 add_action( 'init', array( $this, 'handle_expired_membership' ) );
92 }
93
94 /**
95 * Handle request to save settings.
96 *
97 * @param Request $request The request object.
98 *
99 * @defender_route
100 * @return Response
101 */
102 public function save_settings( Request $request ) {
103 $data = $request->get_data(
104 array(
105 'enabled' => array( 'type' => 'bool' ),
106 'managed_by' => array( 'type' => 'string' ),
107 // Temporary property.
108 'module_title' => array(
109 'type' => 'string',
110 'sanitize' => 'sanitize_text_field',
111 ),
112 'module_location' => array(
113 'type' => 'string',
114 'sanitize' => 'sanitize_text_field',
115 ),
116 // End.
117 )
118 );
119 $old_enabled = (bool) $this->model->enabled;
120
121 $location = 'Feature Page';
122 // Split module's titles and locations.
123 if ( isset( $data['module_title'] ) && 'antibot' === $data['module_title'] ) {
124 if ( empty( $data['module_location'] ) ) {
125 $location = 'Dashboard';
126 } else {
127 $location = $data['module_location'];
128 unset( $data['module_location'] );
129 }
130 unset( $data['module_title'] );
131 }
132
133 $this->model->import( $data );
134 if ( $this->model->validate() ) {
135 $this->model->save();
136
137 if ( 'plugin' === $this->service->get_managed_by() ) {
138 if ( $old_enabled !== $this->model->enabled ) {
139 if ( ! $this->model->enabled ) {
140 $this->service->delete_blocklist();
141 } elseif ( $this->model->enabled && $this->service->maybe_download() ) {
142 $this->service->download_and_store_blocklist();
143 }
144 }
145 } else {
146 $old_enabled = $this->service->frontend_is_enabled();
147 $result = $this->service->toggle_on_hosting( $this->model->enabled );
148
149 if ( is_wp_error( $result ) ) {
150 return new Response(
151 false,
152 array( 'message' => esc_html__( 'Something wrong happened, please try again!', 'defender-security' ) )
153 );
154 }
155 }
156
157 Config_Hub_Helper::set_clear_active_flag();
158
159 // Hide the Antibot notice on the Dashboard page.
160 if ( ! empty( $data['enabled'] ) ) {
161 delete_site_option( Antibot_Global_Firewall_Component::NOTICE_SLUG );
162 }
163 // Maybe track.
164 if ( ! defender_is_wp_cli() && $old_enabled !== $data['enabled'] ) {
165 wd_di()->get( Antibot_Analytics::class )->track_antibot( $old_enabled, $location );
166 }
167
168 $update_message = $this->get_update_message( $data, $old_enabled, Antibot_Global_Firewall_Setting::get_module_name() );
169 $referrer = wp_get_referer();
170 if ( $referrer && strpos( $referrer, 'page=wp-defender' ) !== false ) {
171 $update_message = sprintf(
172 /* translators: 1: Bold open tag, 2: Bold close tag */
173 __( '%s is now enabled.', 'defender-security' ),
174 '<strong>' . Antibot_Global_Firewall_Setting::get_module_name() . '</strong>'
175 );
176 }
177 return new Response(
178 true,
179 array_merge(
180 array(
181 'message' => $update_message,
182 'auto_close' => true,
183 ),
184 $this->data_frontend()
185 )
186 );
187 }
188
189 return new Response(
190 false,
191 array( 'message' => $this->model->get_formatted_errors() )
192 );
193 }
194
195 /**
196 * Hide the Antibot notice.
197 *
198 * @return Response
199 * @defender_route
200 */
201 public function hide_antibot_notice(): Response {
202 delete_site_option( Antibot_Global_Firewall_Component::NOTICE_SLUG );
203
204 return new Response( true, array() );
205 }
206
207 /**
208 * Queue assets and require data.
209 *
210 * @return void
211 */
212 public function enqueue_assets() {
213 if ( $this->is_page_active() ) {
214 wp_localize_script( 'def-iplockout', 'antibot', $this->data_frontend() );
215 }
216 }
217
218 /**
219 * All the variables that we will show on frontend, both in the main page, or dashboard widget.
220 *
221 * @return array
222 */
223 public function data_frontend(): array {
224 /**
225 * Show the onboarding reminder notice if:
226 * 1. If the reminder time is set and the time difference is greater than a week.
227 * 2. No click on the Cross icon before.
228 */
229 $is_reminder = false;
230 $last_reminder = get_site_option( Onboard::REMINDER_KEY, 0 );
231 if ( ! empty( $last_reminder ) ) {
232 $time_diff = time() - $last_reminder;
233 if ( $time_diff > WEEK_IN_SECONDS ) {
234 $is_reminder = true;
235 }
236 }
237
238 $model_export = $this->model->export();
239 $model_export['managed_by'] = $this->service->get_managed_by();
240 $module_name = Antibot_Global_Firewall_Setting::get_module_name();
241
242 return array_merge(
243 array(
244 'model' => $model_export,
245 'misc' => array(
246 'module_slug' => Antibot_Global_Firewall_Setting::get_module_slug(),
247 'module_name' => $module_name,
248 'show_notice' => $is_reminder
249 && (bool) get_site_option( Antibot_Global_Firewall_Component::NOTICE_SLUG, false ),
250 'sync_schedule' => __( 'Twice Daily', 'defender-security' ),
251 'ips_count' => $this->service->get_blocklisted_ip_count(),
252 'frontend_is_enabled' => $this->service->frontend_is_enabled(),
253 'frontend_mode' => $this->service->frontend_mode(),
254 'is_active' => $this->service->is_active(),
255 'active_tooltip_text' => __( 'List of exploit attempts detected and blocked across all connected sites by AntiBot Firewall.', 'defender-security' ),
256 'inactive_tooltip_text' => sprintf(
257 /* translators: %s: Module name. */
258 __( '%s is Inactive.', 'defender-security' ),
259 $module_name
260 ),
261 'current_user' => esc_html( wp_get_current_user()->display_name ?? __( 'User', 'defender-security' ) ),
262 'current_plan' => $this->get_membership_type(),
263 'is_expired_membership' => $this->is_expired_membership_type(),
264 ),
265 ),
266 $this->dump_routes_and_nonces()
267 );
268 }
269
270 /**
271 * Download and store blocklist.
272 *
273 * @return void
274 */
275 public function handle_download_and_store_blocklist(): void {
276 if ( is_multisite() ) {
277 $next_run = get_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION, 0 );
278 if ( ! empty( $next_run ) && $next_run > time() ) {
279 return;
280 }
281
282 $interval = wd_di()->get( Scheduler::class )->get_cron_schedule_interval( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_SCHEDULE );
283 $next_run = time() + ( ! empty( $interval ) ? $interval : 12 * HOUR_IN_SECONDS );
284 update_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION, $next_run );
285 }
286
287 if ( true === $this->service->is_active_via_plugin() ) {
288 $is_switch_to_main_site = is_multisite() && ! is_main_site();
289 if ( $is_switch_to_main_site ) {
290 switch_to_blog( get_main_site_id() );
291 }
292
293 $this->service->download_and_store_blocklist();
294
295 if ( $is_switch_to_main_site ) {
296 restore_current_blog();
297 }
298 }
299 }
300
301 /**
302 * Handle the HCM connection attempt via Antibot page.
303 *
304 * @return void
305 */
306 public function maybe_hcm_connection_attempt() {
307 $data = get_site_transient( Hub_Connector::TRANSIENT_KEY );
308 $module_slug = $data['module_slug'] ?? '';
309
310 if ( Antibot_Global_Firewall_Setting::get_module_slug() === $module_slug && self::get_status() ) {
311 delete_site_transient( Hub_Connector::TRANSIENT_KEY );
312
313 if ( 'plugin' === $this->service->get_managed_by() ) {
314 $this->service->managed_by_plugin_action();
315 } else {
316 $this->service->managed_by_hosting_action();
317 }
318 }
319 }
320
321 /**
322 * Export the data of this module, we will use this for export to HUB, create a preset etc.
323 */
324 public function to_array() {}
325
326 /**
327 * Import the data from the HUB, or from the preset.
328 *
329 * @param array $data The data to be imported.
330 *
331 * @return void
332 */
333 public function import_data( $data ) {
334 $model = $this->model;
335 if ( isset( $data['antibot'] ) ) {
336 $model->enabled = (bool) $data['antibot'];
337 } else {
338 $model->enabled = true;
339 }
340 $model->save();
341 }
342
343 /**
344 * Remove all settings, configs generated in this container runtime.
345 */
346 public function remove_settings() {}
347
348 /**
349 * Remove all data.
350 */
351 public function remove_data() {
352 $this->service->delete_blocklist();
353
354 delete_site_option( Antibot_Global_Firewall_Component::NOTICE_SLUG );
355 delete_site_option( Antibot_Global_Firewall_Component::DOWNLOAD_SYNC_NEXT_RUN_OPTION );
356 delete_site_transient( Antibot_Global_Firewall_Component::BLOCKLIST_STATS_KEY . '_' . Antibot_Global_Firewall_Setting::MODE_BASIC );
357 delete_site_transient( Antibot_Global_Firewall_Component::BLOCKLIST_STATS_KEY . '_' . Antibot_Global_Firewall_Setting::MODE_STRICT );
358 delete_site_transient( Antibot_Global_Firewall_Component::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS );
359 }
360
361 /**
362 * Export strings.
363 *
364 * @return array
365 */
366 public function export_strings() {
367 return array();
368 }
369
370 /**
371 * Disconnect site from HUB.
372 *
373 * @defender_route
374 * @return Response
375 */
376 public function disconnect_site(): Response {
377 $this->logout();
378 return new Response( true, array( 'message' => __( 'Your site has been disconnected successfully!', 'defender-security' ) ) );
379 }
380
381 /**
382 * Handle request to switch managed by.
383 *
384 * @defender_route
385 * @return Response
386 */
387 public function switch_managed_by(): Response {
388 $result = $this->service->switch_managed_by();
389 if ( false !== $result ) {
390 return new Response(
391 true,
392 array(
393 'message' => sprintf(
394 /* translators: 1. Open tag. 2. Close tag. 3. Managed by label. */
395 __( '%1$sAntiBot%2$s blocklist is now being managed by %3$s.', 'defender-security' ),
396 '<strong>',
397 '</strong>',
398 $this->service->get_managed_by_label()
399 ),
400 'managed_by' => $result,
401 'frontend_mode' => $this->service->frontend_mode(),
402 'ips_count' => $this->service->get_blocklisted_ip_count(),
403 'auto_close' => true,
404 )
405 );
406 }
407
408 return new Response(
409 false,
410 array(
411 'message' => __( 'Failed to switch. Please try again.', 'defender-security' ),
412 'auto_close' => true,
413 )
414 );
415 }
416
417 /**
418 * Various actions during form submission.
419 *
420 * @param Request $request The HTTP request object.
421 *
422 * @defender_route
423 * @return Response
424 */
425 public function blocklist_checker_submit_trusted_ip_form( Request $request ): Response {
426 $data = $request->get_data(
427 array(
428 'ip' => array(
429 'type' => 'string',
430 'sanitize' => 'sanitize_text_field',
431 ),
432 'email' => array(
433 'type' => 'string',
434 'sanitize' => 'sanitize_text_field',
435 ),
436 'service' => array(
437 'type' => 'string',
438 'sanitize' => 'sanitize_text_field',
439 ),
440 'reason' => array(
441 'type' => 'string',
442 'sanitize' => 'sanitize_text_field',
443 ),
444 )
445 );
446 // Logging.
447 $message = sprintf(
448 'IP: %s, email: %s, service: %s, reason: %s',
449 $data['ip'],
450 $data['email'],
451 $data['service'],
452 $data['reason']
453 );
454 $this->service->log_ip_message( $message );
455
456 return new Response( true, array() );
457 }
458
459 /**
460 * Check if an IP exists in the Local, Custom and AntiBot blocklist.
461 *
462 * @param Request $request The HTTP request object.
463 *
464 * @defender_route
465 * @return Response
466 */
467 public function blocklist_checker_search( Request $request ): Response {
468 $data = $request->get_data(
469 array(
470 'ip' => array(
471 'type' => 'string',
472 'sanitize' => 'sanitize_text_field',
473 ),
474 )
475 );
476
477 if ( false === filter_var( $data['ip'], FILTER_VALIDATE_IP ) ) {
478 return new Response(
479 false,
480 array(
481 'success' => false,
482 'show_notice' => false,
483 'message' => __( 'Invalid IP address.', 'defender-security' ),
484 )
485 );
486 }
487
488 $bl_service = wd_di()->get( Blacklist_Lockout::class );
489 $gi_service = wd_di()->get( Global_IP::class );
490
491 return new Response(
492 true,
493 array(
494 'success' => true,
495 'show_notice' => false,
496 'message' => __( 'The IP address is searched successfully.', 'defender-security' ),
497 'local' => $bl_service->is_blacklist( $data['ip'] ),
498 'central' => $gi_service->is_global_ip_enabled() && $gi_service->is_ip_blocked( $data['ip'] ),
499 'antibot' => $this->service->is_ip_blocked( $data['ip'] ),
500 )
501 );
502 }
503
504 /**
505 * Add IP to AntiBot allowlist.
506 *
507 * @param Request $request The HTTP request object.
508 *
509 * @defender_route
510 * @return Response
511 */
512 public function blocklist_checker_add_ip_to_allowlist( Request $request ): Response {
513 $data = $request->get_data(
514 array(
515 'ip' => array(
516 'type' => 'string',
517 'sanitize' => 'sanitize_text_field',
518 ),
519 )
520 );
521 $ip = $data['ip'];
522 if ( false === filter_var( $ip, FILTER_VALIDATE_IP ) ) {
523 return new Response(
524 false,
525 array(
526 'message' => esc_html__( 'Invalid IP address.', 'defender-security' ),
527 )
528 );
529 }
530 $collection = 'allowlist';
531 // Add to Local allowlist.
532 $model = wd_di()->get( \WP_Defender\Model\Setting\Blacklist_Lockout::class );
533 if ( ! $model->is_ip_in_list( $ip, $collection ) ) {
534 $model->add_to_list( $ip, $collection );
535 }
536 // Add to Custom IP allowlist.
537 $global_ip_service = wd_di()->get( Global_IP::class );
538 if ( $global_ip_service->can_central_ip_autosync() ) {
539 $data = array(
540 'allow_list' => array( $ip ),
541 );
542
543 $res = $global_ip_service->add_to_global_ip_list( $data );
544 if ( is_wp_error( $res ) ) {
545 return new Response(
546 false,
547 array(
548 'message' => $res->get_error_message(),
549 )
550 );
551 }
552 }
553
554 return new Response(
555 true,
556 array(
557 'message' => sprintf(
558 /* translators: 1: IP address. 2: Opening anchor tag. 3: Closing anchor tag. */
559 esc_html__(
560 'IP %1$s has been added to your Site\'s allowlist. You can manage it in %2$sIP Lockouts%3$s.',
561 'defender-security'
562 ),
563 $ip,
564 '<a href="' . network_admin_url( 'admin.php?page=wdf-ip-lockout&view=blocklist#tab-ip-allowlist' ) . '">',
565 '</a>'
566 ),
567 'interval' => 5,
568 )
569 );
570 }
571
572 /**
573 * Sync AntiBot status with Hosting.
574 * If AntiBot is enabled in Hosting, update the 'managed_by' and 'enabled' status.
575 * If AntiBot is disabled in Hosting, do nothing.
576 *
577 * @return void
578 */
579 public function sync_state(): void {
580 $hosting_enabled = defender_get_hosting_feature_state( 'antibot' );
581
582 if (
583 true === $hosting_enabled
584 && 'plugin' === $this->model->managed_by
585 && true !== get_site_transient( Antibot_Global_Firewall_Component::IS_SWITCHING_TO_PLUGIN_IN_PROGRESS )
586 ) {
587 $this->model->enabled = false;
588 $this->model->managed_by = 'hosting';
589 $this->model->save();
590 }
591 }
592
593 /**
594 * Handle request to switch mode.
595 *
596 * @defender_route
597 * @return Response
598 */
599 public function switch_mode(): Response {
600 $result = $this->process_switch_mode();
601 if ( $result['success'] ) {
602 if ( $this->maybe_track() ) {
603 wd_di()->get( Antibot_Analytics::class )->track_antibot( false, 'Feature Page' );
604 }
605 return new Response( true, $result['data'] );
606 }
607
608 return new Response( false, $result['data'] );
609 }
610
611 /**
612 * Process request to switch mode.
613 *
614 * @return array
615 */
616 public function process_switch_mode(): array {
617 $result = $this->service->switch_mode();
618 if ( false !== $result && ! is_wp_error( $result ) ) {
619 return array(
620 'success' => true,
621 'data' => array(
622 'message' => sprintf(
623 /* translators: 1. Open tag. 2. Close tag. 3. Mode. */
624 __( '%1$sAntiBot Firewall%2$s mode updated – You\'re now using %3$s Mode.', 'defender-security' ),
625 '<strong>',
626 '</strong>',
627 ucfirst( $result )
628 ),
629 'mode' => $result,
630 'ips_count' => $this->service->get_blocklisted_ip_count(),
631 'auto_close' => true,
632 ),
633 );
634 }
635
636 return array(
637 'success' => false,
638 'data' => array(
639 'message' => __( 'Failed to switch. Please try again.', 'defender-security' ),
640 'auto_close' => true,
641 ),
642 );
643 }
644
645 /**
646 * Handle expired membership by automatically disabling the AntiBot Global Firewall module.
647 * Logs the action when the feature is disabled due to expired membership.
648 *
649 * @return void
650 */
651 public function handle_expired_membership(): void {
652 if ( $this->is_expired_membership_type() && $this->model->enabled ) {
653 $this->service->managed_by_plugin_action( false );
654 $this->log( 'AntiBot Global Firewall automatically disabled due to expired membership.', Antibot_Global_Firewall_Component::LOG_FILE_NAME );
655 }
656 }
657 }
658