PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.0
Defender Security – Malware Scanner, Login Security & Firewall v5.7.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 / component / ip / class-global-ip.php

class-global-ip.php in Defender Security – Malware Scanner, Login Security & Firewall 5.7.0, at src/component/ip/class-global-ip.php

599 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handles global IP functionalities including allow and block lists.
4 *
5 * @package WP_Defender\Component\IP
6 */
7
8 namespace WP_Defender\Component\IP;
9
10 use WP_Error;
11 use Exception;
12 use WP_Defender\Component;
13 use WP_Defender\Behavior\WPMUDEV;
14 use WP_Defender\Controller\Firewall;
15 use WP_Defender\Model\Lockout_Log;
16 use WP_Defender\Model\Setting\Global_Ip_Lockout;
17 use WP_Defender\Traits\Defender_Dashboard_Client;
18 use WP_Defender\Traits\Country;
19
20 /**
21 * Handles global IP functionalities including allow and block lists.
22 */
23 class Global_IP extends Component {
24
25 use Defender_Dashboard_Client;
26 use Country;
27
28 public const REASON_SLUG = 'global_ip';
29 /**
30 * Global IP list key.
31 */
32 public const LIST_KEY = 'wpdef_global_ip_list';
33 public const LIST_LAST_SYNCED_KEY = 'wpdef_global_ip_list_last_synced';
34 public const DASHBOARD_NOTICE_REMINDER = 'wpdef_global_ip_dashoard_notice_reminder';
35
36 /**
37 * The list of allowed IPs.
38 *
39 * @var array
40 */
41 private $allow_list = array();
42
43 /**
44 * The list of blocked IPs.
45 *
46 * @var array
47 */
48 private $block_list = array();
49
50 /**
51 * Fetches data from HUB API service method.
52 *
53 * @var array
54 */
55 private $global_list = array();
56
57 /**
58 * Instance of WPMUDEV class.
59 *
60 * @var WPMUDEV
61 */
62 private $wpmudev;
63
64 /**
65 * The model for global IP lockout settings.
66 *
67 * @var Global_Ip_Lockout
68 */
69 protected $model;
70
71 /**
72 * Initializes the WPMUDEV and Global_Ip_Lockout instances and sets up the initial state.
73 */
74 public function __construct() {
75 $this->wpmudev = wd_di()->get( WPMUDEV::class );
76 $this->model = wd_di()->get( Global_Ip_Lockout::class );
77 $this->init();
78 }
79
80 /**
81 * Initialize
82 *
83 * @return void
84 */
85 public function init(): void {
86 $global_ip_list = $this->get_global_ip_list();
87
88 $this->global_list = is_array( $global_ip_list ) ? $global_ip_list : array();
89 $this->allow_list = $global_ip_list['allow_list'] ?? array();
90 $this->block_list = $global_ip_list['block_list'] ?? array();
91 }
92
93 /**
94 * Return global ip allow list from HUB.
95 */
96 public function allow_list(): array {
97 return $this->allow_list;
98 }
99
100 /**
101 * Return global ip block list from HUB.
102 */
103 public function block_list(): array {
104 return $this->block_list;
105 }
106
107 /**
108 * Verify is given ip exists in global IP allow list.
109 *
110 * @param string $ip ip address.
111 *
112 * @return bool if exists return true else false.
113 */
114 public function is_ip_allowed( string $ip ): bool {
115 return $this->is_ip_in_format( $ip, $this->allow_list() );
116 }
117
118 /**
119 * Verify is given ip exists in global IP block list.
120 *
121 * @param string $ip ip address.
122 *
123 * @return bool if exists return true else false.
124 */
125 public function is_ip_blocked( string $ip ): bool {
126 return $this->is_ip_in_format( $ip, $this->block_list() );
127 }
128
129 /**
130 * Check if Global IP is enabled.
131 *
132 * @return bool True for enabled or false for disabled.
133 */
134 public function is_global_ip_enabled(): bool {
135 return $this->model->enabled;
136 }
137
138 /**
139 * Check if Custom IP lists can be synced automatically.
140 *
141 * @return bool True for enabled or false for disabled.
142 */
143 public function is_blocklist_autosync_enabled(): bool {
144 return $this->model->blocklist_autosync;
145 }
146
147 /**
148 * Check if permanently blocked IPs can be synced with HUB.
149 *
150 * @return bool
151 */
152 public function can_blocklist_autosync(): bool {
153 return $this->wpmudev->is_dash_activated() &&
154 $this->wpmudev->is_site_connected_to_hub() &&
155 $this->is_global_ip_enabled() &&
156 $this->is_blocklist_autosync_enabled();
157 }
158
159 /**
160 * Check if blocked or allowlisted IPs can be synced with the Custom IP lists on HUB.
161 *
162 * @return bool
163 */
164 public function can_central_ip_autosync(): bool {
165 return $this->wpmudev->hub_connector_connected() &&
166 $this->is_global_ip_enabled() &&
167 $this->is_blocklist_autosync_enabled();
168 }
169
170 /**
171 * Get Global IP list from DB.
172 *
173 * @return mixed
174 */
175 public function get_global_ip_list() {
176 return get_site_transient( self::LIST_KEY );
177 }
178
179 /**
180 * Set Global IP list.
181 *
182 * @param array $data The data containing the allow list, block list, last update time, and last update time UTC.
183 *
184 * @return bool|WP_Error
185 */
186 public function set_global_ip_list( array $data ) {
187 if (
188 ! isset( $data['allow_list'] ) ||
189 ! isset( $data['block_list'] ) ||
190 ! isset( $data['last_update_time'] ) ||
191 ! isset( $data['last_update_time_utc'] )
192 ) {
193 return new WP_Error( 'defender_hub_api_missing_params', esc_html__( 'Missing parameter(s)', 'defender-security' ) );
194 }
195
196 $allow_list = array();
197 $block_list = array();
198 $errors = array(
199 'allow_list' => array(),
200 'block_list' => array(),
201 );
202
203 if ( is_array( $data['allow_list'] ) ) {
204 foreach ( $data['allow_list'] as $key => $ip ) {
205 $error = $this->display_validation_message( $ip );
206
207 if ( ! empty( $error ) ) {
208 $errors['allow_list'] = array_merge( $error, $errors['allow_list'] );
209 unset( $data['allow_list'][ $key ] );
210 }
211 }
212 $allow_list = $data['allow_list'];
213 }
214
215 if ( is_array( $data['block_list'] ) ) {
216 foreach ( $data['block_list'] as $key => $ip ) {
217 $error = $this->display_validation_message( $ip );
218
219 if ( ! empty( $error ) ) {
220 $errors['block_list'] = array_merge( $error, $errors['block_list'] );
221 unset( $data['block_list'][ $key ] );
222 }
223 }
224 $block_list = $data['block_list'];
225 }
226
227 $value = array(
228 'allow_list' => $allow_list,
229 'block_list' => $block_list,
230 'last_update_time' => $data['last_update_time'] ?? '',
231 'last_update_time_utc' => $data['last_update_time_utc'] ?? '',
232 );
233 $ret = set_site_transient( self::LIST_KEY, $value );
234 $this->global_list = $value;
235 $this->allow_list = $value['allow_list'];
236 $this->block_list = $value['block_list'];
237
238 self::set_last_synced();
239
240 if ( ! empty( $errors['allow_list'] ) || ! empty( $errors['block_list'] ) ) {
241 return new WP_Error( 'defender_hub_api_invalid_ips', esc_html__( 'Invalid IP(s)', 'defender-security' ), $errors );
242 } else {
243 return $ret;
244 }
245 }
246
247 /**
248 * Format Global IP list for frontend.
249 *
250 * @return array
251 */
252 public function get_formated_global_ip_list(): array {
253 $data = $this->global_list;
254 $last_synced = self::get_last_synced();
255
256 return array(
257 'allow_list' => ! empty( $data['allow_list'] ) && is_array( $data['allow_list'] ) ?
258 implode( '<br>', $data['allow_list'] ) :
259 '',
260 'block_list' => ! empty( $data['block_list'] ) && is_array( $data['block_list'] ) ?
261 implode( '<br>', $data['block_list'] ) :
262 '',
263 'last_synced' => 0 !== $last_synced ?
264 $this->format_date_time( $last_synced ) :
265 esc_html__( 'Never', 'defender-security' ),
266 'block_list_count' => ! empty( $data['block_list'] ) && is_array( $data['block_list'] ) ? $this->format_number( count( $data['block_list'] ) ) : 0,
267 'last_update_time_utc' => ! empty( $data['last_update_time_utc'] ) ?
268 $this->format_date_time( $data['last_update_time_utc'] ) :
269 esc_html__( 'Never', 'defender-security' ),
270 'last_update_time' => ! empty( $data['last_update_time'] ) ?
271 $this->format_date_time( $data['last_update_time'] ) :
272 esc_html__( 'Never', 'defender-security' ),
273 'is_synced_before' => ! empty( $data ),
274 );
275 }
276
277 /**
278 * Fetch Global IP list from HUB if DB has old data.
279 *
280 * @return array|WP_Error On success return global ip list or on failure return WP_Error object.
281 */
282 public function fetch_global_ip_list() {
283 $data = $this->get_global_ip_list();
284
285 if ( ! is_array( $data ) ) {
286 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
287
288 try {
289 $data = $this->make_wpmu_free_request( WPMUDEV::API_GLOBAL_IP_LIST );
290 } catch ( Exception $e ) {
291 return new WP_Error( 'defender_hub_api_invalid_returned', $e->getMessage() );
292 }
293
294 if ( is_array( $data ) ) {
295 $this->set_global_ip_list( $data );
296 }
297 } else {
298 $updated_time = $this->fetch_global_ip_list_updated_time();
299
300 if (
301 is_wp_error( $updated_time ) ||
302 empty( $updated_time['last_update_time_utc'] ) ||
303 empty( $data['last_update_time_utc'] ) ||
304 strtotime( $updated_time['last_update_time_utc'] ) > strtotime( $data['last_update_time_utc'] )
305 ) {
306 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
307
308 try {
309 $data = $this->make_wpmu_free_request( WPMUDEV::API_GLOBAL_IP_LIST );
310 } catch ( Exception $e ) {
311 return new WP_Error( 'defender_hub_api_invalid_returned', $e->getMessage() );
312 }
313
314 if ( is_array( $data ) ) {
315 $this->set_global_ip_list( $data );
316 }
317 } else {
318 self::set_last_synced();
319 }
320 }
321
322 if ( ! is_array( $data ) ) {
323 $this->log( 'Global IP API Error: Fetch Global IP list', Firewall::FIREWALL_LOG );
324 $this->log( $data, Firewall::FIREWALL_LOG );
325
326 return new WP_Error(
327 'defender_hub_api_invalid_returned',
328 esc_html__( 'API returned invalid data format.', 'defender-security' )
329 );
330 }
331
332 return $data;
333 }
334
335 /**
336 * Fetch Global IP list updated time from HUB.
337 *
338 * @return array|WP_Error
339 */
340 public function fetch_global_ip_list_updated_time() {
341 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
342
343 return $this->make_wpmu_request(
344 WPMUDEV::API_GLOBAL_IP_LIST,
345 array(
346 '_fields' => 'last_update_time_utc',
347 )
348 );
349 }
350
351 /**
352 * Add one or more IPs to the allow_list and/or block_list.
353 *
354 * @param array $params The IPs to add.
355 *
356 * @since 4.12.0 Send a request using API key from the Dashboard plugin or HCM.
357 * @return array|WP_Error
358 */
359 public function add_to_global_ip_list( array $params ) {
360 $is_allow_list = ! empty( $params['allow_list'] );
361 $is_block_list = ! empty( $params['block_list'] );
362 if ( ! $is_allow_list && ! $is_block_list ) {
363 return new WP_Error(
364 'global_ip_invalid_params',
365 esc_html__( 'Invalid Global IP parameter(s) provided.', 'defender-security' )
366 );
367 }
368
369 $data = array();
370 if ( $is_allow_list ) {
371 $data['allow_list'] = (array) $params['allow_list'];
372 }
373 if ( $is_block_list ) {
374 $data['block_list'] = (array) $params['block_list'];
375 }
376
377 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
378 $ret = $this->make_wpmu_request(
379 WPMUDEV::API_GLOBAL_IP_LIST,
380 $data,
381 array(
382 'method' => 'POST',
383 )
384 );
385
386 if ( is_array( $ret ) ) {
387 $this->set_global_ip_list( $ret );
388 } else {
389 $this->log( 'Global IP API Error: Add IP(s) to allow_list and/or block_list', Firewall::FIREWALL_LOG );
390 $this->log( $ret, Firewall::FIREWALL_LOG );
391 }
392
393 return $ret;
394 }
395
396 /**
397 * Get welcome modal closed timestamp.
398 *
399 * @return false|int
400 */
401 public function get_dashboard_notice_reminder() {
402 return get_site_option( self::DASHBOARD_NOTICE_REMINDER, false );
403 }
404
405 /**
406 * Delete welcome modal closed timestamp.
407 *
408 * @return void
409 */
410 public function delete_dashboard_notice_reminder(): void {
411 delete_site_option( self::DASHBOARD_NOTICE_REMINDER );
412 }
413
414 /**
415 * Check if notice on Defender dashboard can be displayed.
416 *
417 * @return bool
418 */
419 public function is_show_dashboard_notice(): bool {
420 $reminder = $this->get_dashboard_notice_reminder();
421 if ( $this->is_global_ip_enabled() || empty( $reminder ) || time() < $reminder ) {
422 return false;
423 }
424
425 $is_pro = $this->wpmudev->is_pro();
426 $user_roles = is_user_logged_in() ? $this->get_roles( wp_get_current_user() ) : array();
427
428 $is_show_to_user = false;
429 if ( ! $is_pro && ! empty(
430 array_intersect(
431 $user_roles,
432 array(
433 $this->super_admin_slug,
434 'administrator',
435 )
436 )
437 ) ) {
438 $is_show_to_user = true;
439 } elseif ( $is_pro && $this->is_wpmu_dev_admin() ) {
440 $is_show_to_user = true;
441 }
442
443 return $is_show_to_user;
444 }
445
446 /**
447 * Format a number to a human-readable format.
448 *
449 * @param int|float|null $number The number to format.
450 *
451 * @return string The formatted number.
452 */
453 public function format_number( $number ) {
454 if ( null === $number || ! is_numeric( $number ) ) {
455 return '';
456 }
457
458 if ( $number >= 1000000000 ) {
459 return $number / 1000000000 . 'b';
460 } elseif ( $number >= 1000000 ) {
461 return $number / 1000000 . 'm';
462 } elseif ( $number >= 1000 ) {
463 return $number / 1000 . 'k';
464 } else {
465 return $number;
466 }
467 }
468
469 /**
470 * Remove one or more IPs from the block_list.
471 *
472 * @param array $ips The IPs to remove.
473 *
474 * @return array|WP_Error
475 */
476 public function remove_from_blocklist( array $ips ) {
477 if ( empty( $ips ) ) {
478 return new WP_Error( 'defender_hub_api_invalid_ips', esc_html__( 'No IP(s) provided.', 'defender-security' ) );
479 }
480
481 $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
482
483 try {
484 $data = $this->make_wpmu_free_request( WPMUDEV::API_GLOBAL_IP_LIST );
485 } catch ( Exception $e ) {
486 return new WP_Error( 'defender_hub_api_invalid_returned', $e->getMessage() );
487 }
488
489 if ( ! isset( $data['block_list'] ) ) {
490 return new WP_Error(
491 'defender_hub_api_missing_blocklist',
492 esc_html__( 'Missing blocklist data.', 'defender-security' )
493 );
494 }
495
496 $data['block_list'] = (array) $data['block_list'];
497 foreach ( $ips as $ip ) {
498 $key = array_search( $ip, $data['block_list'], true );
499 if ( false !== $key ) {
500 unset( $data['block_list'][ $key ] );
501 }
502 }
503 // Reindex the array.
504 $data['block_list'] = array_values( $data['block_list'] );
505
506 $ret = $this->make_wpmu_request(
507 WPMUDEV::API_GLOBAL_IP_LIST,
508 array(
509 'block_list' => $data['block_list'],
510 ),
511 array(
512 'method' => 'PUT',
513 )
514 );
515
516 if ( is_wp_error( $ret ) ) {
517 return new WP_Error( 'defender_hub_api_invalid_returned', $ret->get_error_message() );
518 } else {
519 $this->set_global_ip_list( $ret );
520 }
521
522 return $ret;
523 }
524
525 /**
526 * Set the last sync time.
527 *
528 * @return void
529 */
530 public static function set_last_synced(): void {
531 update_site_option( self::LIST_LAST_SYNCED_KEY, time() );
532 }
533
534 /**
535 * Get the last sync time.
536 *
537 * @return int
538 */
539 public static function get_last_synced(): int {
540 return get_site_option( self::LIST_LAST_SYNCED_KEY, 0 );
541 }
542
543 /**
544 * Check if the Global IP feature is enabled and the site is connected to the HUB.
545 *
546 * @return bool True if the Global IP feature is active, false otherwise.
547 */
548 public function is_active(): bool {
549 return $this->is_global_ip_enabled() && $this->is_site_connected_to_hub_via_hcm_or_dash();
550 }
551
552 /**
553 * Log the event into db.
554 *
555 * @param string $ip The IP address involved in the event.
556 */
557 public function log_event( $ip ): void {
558 $model = wd_di()->get( Lockout_Log::class );
559 $model->ip = $ip;
560 if ( $model->has_recent_ip_log() ) {
561 $this->log( 'Custom IP Blocklist: IP already logged', Firewall::FIREWALL_LOG );
562 return;
563 }
564 $user_agent = defender_get_data_from_request( 'HTTP_USER_AGENT', 's' );
565 $model->user_agent = isset( $user_agent )
566 ? \WP_Defender\Component\User_Agent::fast_cleaning( $user_agent )
567 : null;
568 $model->date = time();
569 $model->blog_id = get_current_blog_id();
570
571 $ip_to_country = $this->ip_to_country( $ip );
572
573 if ( isset( $ip_to_country['iso'] ) ) {
574 $model->country_iso_code = $ip_to_country['iso'];
575 }
576 /* translators: %s is the IP address */
577 $model->log = sprintf( esc_html__( '%s locked out - found in Custom IP Blocklist', 'defender-security' ), $ip );
578
579 $model->type = Lockout_Log::LOCKOUT_IP_CUSTOM;
580 $model->tried = Lockout_Log::LOCKOUT_IP_CUSTOM;
581 $model->save();
582 // We don’t use defender_notify hook for LOCKOUT_IP_CUSTOM.
583 }
584
585 /**
586 * Handle expired membership by automatically disabling the Central IP module.
587 * Logs the action when the feature is disabled due to expired membership.
588 *
589 * @return void
590 */
591 public function handle_expired_membership(): void {
592 if ( $this->is_expired_membership_type() && $this->model->enabled ) {
593 $this->model->enabled = false;
594 $this->model->save();
595 $this->log( 'Central IP automatically disabled due to expired membership.', 'central_ip.log' );
596 }
597 }
598 }
599