PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.6.0
Defender Security – Malware Scanner, Login Security & Firewall v5.6.0
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.6.0, at src/controller/class-antibot-global-firewall.php

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