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 / traits / defender-hub-client.php

defender-hub-client.php in Defender Security – Malware Scanner, Login Security & Firewall 5.4.0, at src/traits/defender-hub-client.php

800 lines 24.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Handle HUB based functionalities of WPMUDEV class.
4 *
5 * @package WP_Defender\Traits
6 */
7
8 namespace WP_Defender\Traits;
9
10 use WP_Error;
11 use DateTime;
12 use Exception;
13 use WP_User_Query;
14 use WPMUDEV_Dashboard;
15 use WP_Defender\Model\Scan;
16 use WP_Defender\Model\Audit_Log;
17 use WP_Defender\Model\Notification;
18 use WP_Defender\Controller\Firewall;
19 use WP_Defender\Component\Quarantine;
20 use WP_Defender\Model\Setting\Two_Fa;
21 use WP_Defender\Component\IP\Antibot_Global_Firewall;
22 use WP_Defender\Model\Setting\Recaptcha;
23 use WP_Defender\Model\Setting\Mask_Login;
24 use WP_Defender\Controller\Security_Tweaks;
25 use WP_Defender\Controller\Security_Headers;
26 use WP_Defender\Model\Setting\Audit_Logging;
27 use WP_Defender\Model\Setting\Login_Lockout;
28 use WP_Defender\Model\Setting\Notfound_Lockout;
29 use WP_Defender\Model\Notification\Audit_Report;
30 use WP_Defender\Model\Setting\Global_Ip_Lockout;
31 use WP_Defender\Model\Setting\User_Agent_Lockout;
32 use WP_Defender\Model\Notification\Malware_Report;
33 use WP_Defender\Model\Setting\Password_Protection;
34 use WP_Defender\Model\Notification\Firewall_Report;
35 use WP_Defender\Model\Setting\Scan as Scan_Settings;
36 use WP_Defender\Model\Notification\Firewall_Notification;
37 use WP_Defender\Model\Setting\Security_Tweaks as Model_Security_Tweaks;
38
39 trait Defender_Hub_Client {
40
41 use IP;
42
43 /**
44 * Get API base URL.
45 *
46 * @return string
47 * @since 3.4.0
48 */
49 public function get_api_base_url(): string {
50 return defined( 'WPMUDEV_CUSTOM_API_SERVER' ) && WPMUDEV_CUSTOM_API_SERVER
51 ? trailingslashit( WPMUDEV_CUSTOM_API_SERVER )
52 : 'https://wpmudev.com/';
53 }
54
55 /**
56 * Retrieves the endpoint URL based on the given scenario.
57 *
58 * @param string $scenario The scenario identifier.
59 *
60 * @return string The endpoint URL.
61 */
62 public function get_endpoint( $scenario ): string {
63 $base = $this->get_api_base_url();
64 switch ( $scenario ) {
65 case self::API_SCAN_KNOWN_VULN:
66 return $base . 'api/defender/v1/vulnerabilities';
67 case self::API_SCAN_SIGNATURE:
68 return $base . 'api/defender/v1/yara-signatures';
69 case self::API_AUDIT:
70 // This is from another endpoint.
71 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
72 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
73 : 'https://audit.wpmudev.org/';
74
75 return $base . 'logs';
76 case self::API_AUDIT_ADD:
77 $base = defined( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
78 ? constant( 'WPMUDEV_CUSTOM_AUDIT_SERVER' )
79 : 'https://audit.wpmudev.org/';
80
81 return $base . 'logs/add_multiple';
82 case self::API_BLACKLIST:
83 return $base . 'api/defender/v1/blacklist-monitoring?domain=' . network_site_url();
84 case self::API_GLOBAL_IP_LIST:
85 return $base . 'api/hub/v1/global-ip-list';
86 case self::API_PACKAGE_CONFIGS:
87 return $base . 'api/hub/v1/package-configs';
88 case self::API_IP_BLOCKLIST_SUBMIT_LOGS:
89 return $base . 'api/blocklist/v1/logs';
90 case self::API_ANTIBOT_GLOBAL_FIREWALL:
91 $site_id = $this->get_site_id();
92
93 return $base . "api/hub/v1/sites/$site_id/modules/hosting/antibot";
94 case self::API_HUB_SYNC:
95 default:
96 return $base . 'api/defender/v1/scan-results';
97 }
98 }
99
100 /**
101 * Get WPMUDEV site id.
102 *
103 * @return int|bool
104 */
105 public function get_site_id() {
106 if ( false !== $this->get_apikey() ) {
107 return (int) WPMUDEV_Dashboard::$api->get_site_id();
108 }
109
110 return false;
111 }
112
113 /**
114 * HUB API remote request method.
115 *
116 * @param string $scenario The scenario for the API request.
117 * @param array $body The body of the API request. Default is an empty array.
118 * @param array $args The arguments for the API request. Default is an empty array.
119 * @param bool $recheck Whether to recheck the API request. Default is false.
120 *
121 * @return array|WP_Error The response body of the API request or a WP_Error object.
122 * @throws Exception If the Dash plugin authentication API key is missing.
123 */
124 private function hub_api_request(
125 string $scenario,
126 array $body = array(),
127 array $args = array(),
128 bool $recheck = false
129 ) {
130 $api_key = $this->get_api_key();
131 $body['domain'] ??= network_site_url();
132
133 $headers = array(
134 'Authorization' => 'Basic ' . $api_key,
135 'apikey' => $api_key,
136 );
137
138 $timeout = isset( $args['timeout'] ) ? $args['timeout'] : 30;
139
140 $args = array_merge(
141 $args,
142 array(
143 'body' => $body,
144 'headers' => $headers,
145 'timeout' => $timeout,
146 'sslverify' => apply_filters( 'https_ssl_verify', true ),
147 )
148 );
149
150 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
151
152 if ( is_wp_error( $request ) ) {
153 if ( ! $recheck ) {
154 return $request;
155 }
156 // Sometimes a response comes with a curl error #52 so should delete Authorization header.
157 $args['headers'] = array( 'apikey' => $api_key );
158 $request = wp_remote_request( $this->get_endpoint( $scenario ), $args );
159 if ( is_wp_error( $request ) ) {
160 return $request;
161 }
162 }
163
164 $result = wp_remote_retrieve_body( $request );
165 $result = json_decode( $result, true );
166
167 if ( 200 !== wp_remote_retrieve_response_code( $request ) ) {
168 return new WP_Error(
169 wp_remote_retrieve_response_code( $request ),
170 $result['message'] ?? wp_remote_retrieve_response_message( $request )
171 );
172 }
173
174 return $result;
175 }
176
177 /**
178 * Makes a request to the WPMU API.
179 *
180 * @param string $scenario The scenario for the request.
181 * @param array $body The body of the request. Default is an empty array.
182 * @param array $args Additional arguments for the request. Default is an empty array.
183 * @param bool $recheck Whether to recheck the request. Default is false.
184 *
185 * @return array|WP_Error The response from the API request.
186 */
187 public function make_wpmu_request(
188 string $scenario,
189 array $body = array(),
190 array $args = array(),
191 bool $recheck = false
192 ) {
193 $api_key = $this->get_api_key();
194
195 if ( empty( $api_key ) ) {
196 $link_text = sprintf(
197 '<a target="_blank" href="%s">%s</a>',
198 'https://wpmudev.com/project/wpmu-dev-dashboard/',
199 esc_html__( 'here', 'defender-security' )
200 );
201
202 return new WP_Error(
203 'dashboard_required',
204 sprintf(
205 /* translators: %s - wpmudev link */
206 esc_html__(
207 'WPMU DEV Dashboard will be required for this action. Please visit %s and install the WPMU DEV Dashboard.',
208 'defender-security'
209 ),
210 $link_text
211 )
212 );
213 }
214
215 return $this->hub_api_request( $scenario, $body, $args, $recheck );
216 }
217
218 /**
219 * Makes a request to the WPMU API for free and higher account holding users.
220 *
221 * @param string $scenario The scenario for the request.
222 * @param array $body The body of the request. Default is an empty array.
223 * @param array $args Additional arguments for the request. Default is an empty array.
224 * @param bool $recheck Whether to recheck the request. Default is false.
225 *
226 * @return array|WP_Error The response from the API request.
227 * @throws Exception If the Dash plugin authentication API key is missing.
228 * @throws Exception If permission is denied for the API call.
229 */
230 public function make_wpmu_free_request(
231 string $scenario,
232 array $body = array(),
233 array $args = array(),
234 bool $recheck = false
235 ) {
236 if ( ! $this->hub_connector_connected() ) {
237 throw new Exception( esc_html__( 'API key missing.', 'defender-security' ) );
238 }
239 if ( $this->can_wpmu_free_request() === false ) {
240 throw new Exception( esc_html__( 'Permission denied API call.', 'defender-security' ) );
241 }
242 return $this->hub_api_request( $scenario, $body, $args, $recheck );
243 }
244
245 /**
246 * Check if the current request can be made as a free API request.
247 *
248 * @return bool True if the request is allowed, false otherwise.
249 * @throws Exception If the WPMU DEV Dashboard plugin is missing.
250 */
251 public function can_wpmu_free_request() {
252 return in_array( $this->get_membership_type(), array( 'free', 'full' ), true );
253 }
254
255 /**
256 * This will build data relate to scan module, so we can push to hub.
257 *
258 * @return array
259 * @since 2.4.7 add 'plugin_integrity' args
260 */
261 protected function build_scan_hub_data(): array {
262 $scan = Scan::get_last();
263 $scan_result = array(
264 'core_integrity' => 0,
265 'plugin_integrity' => 0,
266 'vulnerability_db' => 0,
267 'file_suspicious' => 0,
268 'last_completed' => false,
269 'scan_items' => array(),
270 'num_issues' => 0,
271 'num_ignored_issues' => 0,
272 );
273 $total_issues = 0;
274 if ( is_object( $scan ) ) {
275 $data = $scan->prepare_issues( 10, 1 );
276
277 $scan_result['core_integrity'] = $data['count_core'];
278 $scan_result['plugin_integrity'] = $data['count_plugin'];
279 $scan_result['vulnerability_db'] = $data['count_vuln'];
280 $scan_result['file_suspicious'] = $data['count_malware'];
281 $scan_result['last_completed'] = $scan->date_end;
282 $scan_result['num_ignored_issues'] = $data['count_ignored'];
283
284 if ( ! empty( $data['issues'] ) ) {
285 $total_issues = $data['count_issues'];
286 foreach ( $data['issues'] as $issue ) {
287 $scan_result['scan_items'][] = array(
288 'file' => $issue['full_path'] ?? $issue['file_name'],
289 'detail' => $issue['short_desc'],
290 );
291 }
292 }
293 $scan_result['num_issues'] = $total_issues + $data['count_ignored'];
294 }
295
296 $settings = new Scan_Settings();
297
298 return array(
299 'timestamp' => is_object( $scan ) ? strtotime( $scan->date_start ) : '',
300 'warning' => $total_issues,
301 'scan_result' => $scan_result,
302 'scan_schedule' => array(
303 // @since 2.7.0 change scheduled scan logic.
304 'is_activated' => $settings->scheduled_scanning,
305 // Example of frequency, day, time in build_notification_hub_data() method.
306 'time' => $settings->time,
307 'day' => $this->get_notification_day( $settings ),
308 'frequency' => $this->backward_frequency_compatibility( $settings->frequency ),
309 ),
310 );
311 }
312
313 /**
314 * Converts a string frequency to an integer value for backward compatibility.
315 *
316 * @param string $frequency The frequency to convert. Must be one of 'daily', 'weekly', or 'monthly'.
317 *
318 * @return int The converted integer value. Returns 1 for 'daily', 7 for 'weekly', and 30 for 'monthly' (default).
319 */
320 public function backward_frequency_compatibility( string $frequency ): int {
321 switch ( $frequency ) {
322 case 'daily':
323 return 1;
324 case 'weekly':
325 return 7;
326 case 'monthly':
327 default:
328 return 30;
329 }
330 }
331
332 /**
333 * Build data for security tweaks.
334 *
335 * @return array
336 */
337 protected function build_security_tweaks_hub_data(): array {
338 // Exclude direct call of data_frontend().
339 $tweak_arr = wd_di()->get( Model_Security_Tweaks::class )->get_tweak_types();
340
341 $data = array(
342 'cautions' => $tweak_arr['count_issues'],
343 'issues' => array(),
344 'ignore' => array(),
345 'fixed' => array(),
346 );
347 $types = array(
348 Security_Tweaks::STATUS_ISSUES,
349 Security_Tweaks::STATUS_IGNORE,
350 Security_Tweaks::STATUS_RESOLVE,
351 );
352 $view = '';
353 foreach ( $types as $type ) {
354 if ( 'ignore' === $type ) {
355 $view = '&view=ignored';
356 } elseif ( 'fixed' === $type ) {
357 $view = '&view=resolved';
358 }
359 foreach ( wd_di()->get( Security_Tweaks::class )->init_tweaks( $type ) as $slug => $tweak ) {
360 $data[ $type ][] = array(
361 'label' => $tweak->get_label(),
362 'url' => network_admin_url( 'admin.php?page=wdf-hardener' . $view . '#' . $slug ),
363 );
364 }
365 }
366
367 return $data;
368 }
369
370 /**
371 * Builds an array of audit data to be sent to the hub.
372 *
373 * @return array An array containing the number of audit log entries, the timestamp of the
374 * last audit log entry, and a boolean indicating if audit logging is enabled.
375 */
376 public function build_audit_hub_data(): array {
377 $date_from = ( new DateTime( wp_date( 'Y-m-d', strtotime( '-30 days' ) ) ) )->setTime(
378 0,
379 0,
380 0
381 )->getTimestamp();
382 $date_to = ( new DateTime( wp_date( 'Y-m-d' ) ) )->setTime( 23, 59, 59 )->getTimestamp();
383 $month_count = Audit_Log::count( $date_from, $date_to );
384 $last = Audit_Log::get_last();
385 if ( is_object( $last ) ) {
386 $last = wp_date( 'Y-m-d g:i a', $last->timestamp );
387 } else {
388 $last = 'n/a';
389 }
390
391 $settings = new Audit_Logging();
392
393 return array(
394 'month' => $month_count,
395 'last_event' => $last,
396 'enabled' => $settings->is_active(),
397 );
398 }
399
400 /**
401 * Builds an array of lockout data to be sent to the hub.
402 *
403 * @return array
404 */
405 public function build_lockout_hub_data(): array {
406 $firewall = wd_di()->get( Firewall::class )->data_frontend();
407 $antibot = wd_di()->get( Antibot_Global_Firewall::class );
408
409 return array(
410 'last_lockout' => $firewall['last_lockout'],
411 'lp' => wd_di()->get( Login_Lockout::class )->enabled,
412 'lp_week' => $firewall['login']['week'],
413 'nf' => wd_di()->get( Notfound_Lockout::class )->enabled,
414 'nf_week' => $firewall['nf']['week'],
415 'ua' => wd_di()->get( User_Agent_Lockout::class )->enabled,
416 'ua_week' => $firewall['ua']['week'],
417 'global_ip_list_enabled' => wd_di()->get( Global_Ip_Lockout::class )->enabled,
418 'antibot_enabled' => $antibot->frontend_is_enabled(),
419 'antibot_mode' => $antibot->frontend_mode(),
420 );
421 }
422
423 /**
424 * Builds an array of 2fa data to be sent to the hub.
425 *
426 * @return array
427 */
428 public function build_2fa_hub_data(): array {
429 $settings = new Two_Fa();
430 $service = wd_di()->get( \WP_Defender\Component\Two_Fa::class );
431 $query = new WP_User_Query(
432 array(
433 // Look over the network.
434 'blog_id' => 0,
435 'meta_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
436 array(
437 'key' => $service::DEFAULT_PROVIDER_USER_KEY,
438 'value' => array_keys( $service->get_providers() ),
439 'compare' => 'IN',
440 ),
441 ),
442 )
443 );
444 $active_users = array();
445 if ( $query->get_total() > 0 ) {
446 foreach ( $query->get_results() as $obj_user ) {
447 $active_users[] = array(
448 'display_name' => $obj_user->data->display_name,
449 );
450 }
451 }
452
453 return array(
454 'active' => $settings->enabled && count( $settings->user_roles ),
455 'enabled' => $settings->enabled,
456 'active_users' => $active_users,
457 );
458 }
459
460 /**
461 * Builds an array of mask login data to be sent to the hub.
462 *
463 * @return array
464 */
465 public function build_mask_login_hub_data(): array {
466 $settings = new Mask_Login();
467
468 return array(
469 'active' => $settings->is_active(),
470 'masked_url' => $settings->mask_url,
471 );
472 }
473
474 /**
475 * Builds an array of security headers data to be sent to the hub.
476 *
477 * @return array
478 */
479 public function build_recaptcha_hub_data(): array {
480 $settings = new Recaptcha();
481
482 return array(
483 'active' => $settings->is_active(),
484 );
485 }
486
487 /**
488 * Builds an array of password protection data to be sent to the hub.
489 *
490 * @return array
491 */
492 public function build_password_protection_hub_data(): array {
493 $settings = new Password_Protection();
494
495 return array(
496 'active' => $settings->is_active(),
497 );
498 }
499
500 /**
501 * Get the notification day.
502 *
503 * @param object $module_report The report object.
504 *
505 * @return string
506 */
507 private function get_notification_day( $module_report ): string {
508 if ( ! is_object( $module_report ) ) {
509 return '';
510 }
511
512 if ( 'daily' === $module_report->frequency ) {
513 $day = '1';
514 } elseif ( 'weekly' === $module_report->frequency ) {
515 $day = $module_report->day;
516 } else {
517 // For "monthly" frequency.
518 $day = $module_report->day_n;
519 }
520
521 return $day;
522 }
523
524 /**
525 * Frequency format:
526 * if frequency is day, e.g.: 'frequency' => 1, 'day' => '1', 'time' => '20:30'
527 * if frequency is week, e.g.: 'frequency' => 7, 'day' => 'wednesday', 'time' => '14:00'
528 * if frequency is month, e.g.: 'frequency' => 30, 'day' => '4', 'time' => '4:30'
529 *
530 * @return array
531 */
532 public function build_notification_hub_data(): array {
533 $audit_settings = new Audit_Logging();
534 $audit_report = new Audit_Report();
535 $firewall_report = new Firewall_Report();
536 $malware_report = new Malware_Report();
537 $scan_settings = new Scan_Settings();
538
539 return array(
540 'file_scanning' => array(
541 'active' => true,
542 // @since 2.7.0 move scheduled options to Scan settings, but we get status of Malware Scanning - Reporting here.
543 'enabled' => Notification::STATUS_ACTIVE === $malware_report->status,
544 // Report enabled bool value.
545 'frequency' => array(
546 'frequency' => $this->backward_frequency_compatibility( $scan_settings->frequency ),
547 'day' => $this->get_notification_day( $scan_settings ),
548 'time' => $scan_settings->time,
549 ),
550 ),
551 'audit_logging' => array(
552 'active' => $audit_settings->is_active(),
553 'enabled' => Notification::STATUS_ACTIVE === $audit_report->status,
554 'frequency' => array(
555 'frequency' => $this->backward_frequency_compatibility( $audit_report->frequency ),
556 'day' => $this->get_notification_day( $audit_report ),
557 'time' => $audit_report->time,
558 ),
559 ),
560 'ip_lockouts' => array(
561 // Always true as we have blacklist listening.
562 'active' => true,
563 'enabled' => Notification::STATUS_ACTIVE === $firewall_report->status,
564 // Report enabled bool value.
565 'frequency' => array(
566 'frequency' => $this->backward_frequency_compatibility( $firewall_report->frequency ),
567 'day' => $this->get_notification_day( $firewall_report ),
568 'time' => $firewall_report->time,
569 ),
570 ),
571 );
572 }
573
574 /**
575 * Build data for firewall notification.
576 *
577 * @return array
578 */
579 public function build_firewall_notification_hub_data(): array {
580 $firewall_notification = new Firewall_Notification();
581 if ( 'enabled' === $firewall_notification->status ) {
582 $login_lockout = $firewall_notification->configs['login_lockout'];
583 $nf_lockout = $firewall_notification->configs['nf_lockout'];
584 $ua_lockout = $firewall_notification->configs['ua_lockout'] ?? false;
585 } else {
586 $login_lockout = false;
587 $nf_lockout = false;
588 $ua_lockout = false;
589 }
590
591 return array(
592 'firewall' => array(
593 'login_lockout' => $login_lockout,
594 '404_lockout' => $nf_lockout,
595 'ua_lockout' => $ua_lockout,
596 ),
597 );
598 }
599
600 /**
601 * Build security headers hub data.
602 *
603 * @return array
604 */
605 public function build_security_headers_hub_data(): array {
606 $security_headers = wd_di()->get( Security_Headers::class )->get_type_headers();
607
608 return array(
609 'active' => $security_headers['active'],
610 'inactive' => $security_headers['inactive'],
611 );
612 }
613
614 /**
615 * Build data to be sent to the hub.
616 *
617 * @return array
618 */
619 public function build_stats_to_hub(): array {
620 $scan_data = $this->build_scan_hub_data();
621 $tweaks_data = $this->build_security_tweaks_hub_data();
622 $audit_data = $this->build_audit_hub_data();
623 $firewall_data = $this->build_lockout_hub_data();
624 $two_fa = $this->build_2fa_hub_data();
625 $mask_login = $this->build_mask_login_hub_data();
626 $sec_headers = $this->build_security_headers_hub_data();
627 $recaptcha = $this->build_recaptcha_hub_data();
628 $pwned_password = $this->build_password_protection_hub_data();
629 $quarantined_files = $this->build_quarantined_files_hub_data();
630
631 return array(
632 // Domain name.
633 'domain' => network_home_url(),
634 // Last scan date.
635 'timestamp' => $scan_data['timestamp'],
636 // Scan issue count.
637 'warnings' => $scan_data['warning'],
638 // Security tweaks issue count.
639 'cautions' => $tweaks_data['cautions'],
640 'data_version' => wp_date( 'Ymd' ),
641 'scan_data' => wp_json_encode(
642 array(
643 'scan_result' => $scan_data['scan_result'],
644 'hardener_result' => array(
645 'issues' => $tweaks_data[ Security_Tweaks::STATUS_ISSUES ],
646 'ignored' => $tweaks_data[ Security_Tweaks::STATUS_IGNORE ],
647 'resolved' => $tweaks_data[ Security_Tweaks::STATUS_RESOLVE ],
648 ),
649 'scan_schedule' => $scan_data['scan_schedule'],
650 'audit_status' => array(
651 'events_in_month' => $audit_data['month'],
652 'audit_enabled' => $audit_data['enabled'],
653 'last_event_date' => $audit_data['last_event'],
654 ),
655 'audit_page_url' => network_admin_url( 'admin.php?page=wdf-logging' ),
656 'labels' => array(
657 // Todo: maybe should it remove because Scan Settings model has label() method for that?
658 'parent_integrity' => esc_html__( 'File change detection', 'defender-security' ),
659 'core_integrity' => esc_html__( 'Scan core files', 'defender-security' ),
660 'plugin_integrity' => esc_html__( 'Scan plugin files', 'defender-security' ),
661 'vulnerability_db' => esc_html__( 'Known vulnerabilities', 'defender-security' ),
662 'file_suspicious' => esc_html__( 'Suspicious code', 'defender-security' ),
663 ),
664 'scan_page_url' => network_admin_url( 'admin.php?page=wdf-scan' ),
665 'hardener_page_url' => network_admin_url( 'admin.php?page=wdf-hardener' ),
666 'new_scan_url' => network_admin_url( 'admin.php?page=wdf-scan&wdf-action=new_scan' ),
667 'schedule_scans_url' => network_admin_url( 'admin.php?page=wdf-schedule-scan' ),
668 'settings_page_url' => network_admin_url( 'admin.php?page=wdf-settings' ),
669 'ip_lockout_page_url' => network_admin_url( 'admin.php?page=wdf-ip-lockout' ),
670 'last_lockout' => $firewall_data['last_lockout'],
671 'login_lockout_enabled' => $firewall_data['lp'],
672 'login_lockout' => $firewall_data['lp_week'],
673 'lockout_404_enabled' => $firewall_data['nf'],
674 'lockout_404' => $firewall_data['nf_week'],
675 'lockout_ua_enabled' => $firewall_data['ua'],
676 'lockout_ua' => $firewall_data['ua_week'],
677 'total_lockout' => (int) $firewall_data['lp_week'] + (int) $firewall_data['nf_week'] + (int) $firewall_data['ua_week'],
678 'global_ip_list_enabled' => $firewall_data['global_ip_list_enabled'],
679 'antibot_enabled' => $firewall_data['antibot_enabled'],
680 'antibot_mode' => $firewall_data['antibot_mode'],
681 'advanced' => array(
682 // This is moved but still keep here for backward compatibility.
683 'multi_factors_auth' => array(
684 'active' => $two_fa['active'],
685 'enabled' => $two_fa['enabled'],
686 'active_users' => $two_fa['active_users'],
687 ),
688 'mask_login' => array(
689 'activate' => $mask_login['active'],
690 'masked_url' => $mask_login['masked_url'],
691 ),
692 'security_headers' => array(
693 'active' => $sec_headers['active'],
694 'inactive' => $sec_headers['inactive'],
695 ),
696 'google_recaptcha' => array(
697 'active' => $recaptcha['active'],
698 ),
699 'password_protection' => array(
700 'active' => $pwned_password['active'],
701 ),
702 ),
703 'reports' => $this->build_notification_hub_data(),
704 'notifications' => $this->build_firewall_notification_hub_data(),
705 'quarantined_files' => $quarantined_files,
706 )
707 ),
708 );
709 }
710
711 /**
712 * Checks whether we're on WPMU DEV Hosting.
713 *
714 * @return bool
715 */
716 public function is_wpmu_hosting(): bool {
717 return ! empty( $_SERVER['WPMUDEV_HOSTED'] );
718 }
719
720 /**
721 * Checks whether we're on The Free HUB.
722 *
723 * @return bool
724 */
725 protected function is_tfh_account(): bool {
726 return class_exists( 'WPMUDEV_Dashboard' ) &&
727 is_object( WPMUDEV_Dashboard::$api ) &&
728 method_exists( WPMUDEV_Dashboard::$api, 'get_membership_status' ) &&
729 'free' === WPMUDEV_Dashboard::$api->get_membership_status();
730 }
731
732 /**
733 * Check if WPMUDEV Hosted site is connected to The Free HUB.
734 *
735 * @return bool
736 * @since 3.3.0
737 */
738 public function is_hosted_site_connected_to_tfh(): bool {
739 return $this->is_tfh_account() && $this->is_wpmu_hosting();
740 }
741
742 /**
743 * Check if a site from 3rd party hosting is connected to The Free HUB.
744 *
745 * @return bool
746 * @since 3.6.0
747 */
748 public function is_another_hosted_site_connected_to_tfh(): bool {
749 return $this->is_tfh_account() && ! $this->is_wpmu_hosting();
750 }
751
752 /**
753 * Build notification hub data.
754 *
755 * @return array
756 */
757 protected function build_quarantined_files_hub_data(): array {
758 if ( ! class_exists( 'WP_Defender\Component\Quarantine' ) ) {
759 return array();
760 }
761
762 return wd_di()->get( Quarantine::class )->hub_list();
763 }
764
765 /**
766 * Schedule Hub Synchronization event.
767 */
768 public function schedule_hub_sync(): void {
769 if ( ! wp_next_scheduled( 'defender_hub_sync' ) ) {
770 wp_schedule_single_event( time(), 'defender_hub_sync' );
771 }
772 }
773
774 /**
775 * Returns the signup url.
776 * If Dashboard plugin is active the signup url returned will be the Dashboard signup page. Else Hub signup page.
777 *
778 * @return string
779 */
780 public function signup_url(): string {
781 if ( class_exists( 'WPMUDEV_Dashboard' ) && is_object( WPMUDEV_Dashboard::$api ) ) {
782 return add_query_arg(
783 array( 'page' => 'wpmudev' ),
784 is_multisite() ? network_admin_url() : get_admin_url()
785 );
786 }
787
788 return $this->hub_signup_url();
789 }
790
791 /**
792 * Returns the hub's signup url.
793 *
794 * @return string
795 */
796 public function hub_signup_url(): string {
797 return $this->get_api_base_url() . 'register/?signup=defender&defender_url=' . site_url();
798 }
799 }
800