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

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