PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.4.1
Defender Security – Malware Scanner, Login Security & Firewall v5.4.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 / extra / hub-connector / inc / class-admin.php

class-admin.php in Defender Security – Malware Scanner, Login Security & Firewall 5.4.1, at extra/hub-connector/inc/class-admin.php

715 lines 20.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Admin class to process admin side actions.
4 *
5 * @link http://wpmudev.com
6 * @since 1.0.0
7 * @author Joel James <[email protected]>
8 * @package WPMUDEV\Hub\Connector
9 */
10
11 namespace WPMUDEV\Hub\Connector;
12
13 /**
14 * Class Admin
15 */
16 class Admin {
17
18 use Singleton;
19
20 /**
21 * Plugin identifier.
22 *
23 * @var string $plugin
24 */
25 protected $plugin = 'default';
26
27 /**
28 * Admin page screen IDs.
29 *
30 * @var array Screen IDs.
31 */
32 protected $screens = array();
33
34 /**
35 * Extra query arguments for auth URLs.
36 *
37 * @var array Arguments.
38 */
39 protected $extra_args = array();
40
41 /**
42 * Initialize admin class.
43 *
44 * @since 1.0.0
45 */
46 protected function __construct() {
47 // Add required classes to body.
48 add_filter( 'admin_body_class', array( $this, 'add_body_class' ) );
49 // Register UI for a plugin.
50 add_action( 'wpmudev_hub_connector_ui', array( $this, 'render' ) );
51 // Process auth callback from hub.
52 add_action( 'admin_init', array( $this, 'process_auth_callback' ) );
53 // Include WDP ID header.
54 add_filter( 'extra_plugin_headers', array( $this, 'include_wdp_id_header' ) );
55 }
56
57 /**
58 * Get extra query arguments for plugin.
59 *
60 * @since 1.0.0
61 *
62 * @param string $plugin Plugin identifier.
63 *
64 * @return array
65 */
66 public function get_plugin_extra_args( string $plugin ): array {
67 if ( ! empty( $plugin ) && ! empty( $this->extra_args[ $plugin ] ) ) {
68 return $this->extra_args[ $plugin ];
69 }
70
71 return array();
72 }
73
74 /**
75 * Get screen IDs of plugin.
76 *
77 * @since 1.0.0
78 *
79 * @param string $plugin Plugin identifier.
80 *
81 * @return array
82 */
83 public function get_plugin_screens( string $plugin ): array {
84 if ( ! empty( $plugin ) && ! empty( $this->screens[ $plugin ] ) ) {
85 return $this->screens[ $plugin ];
86 }
87
88 return array();
89 }
90
91 /**
92 * Set extra query arguments URL redirects.
93 *
94 * @since 1.0.0
95 *
96 * @param string $plugin Plugin identifier.
97 * @param array $options Options.
98 *
99 * @return void
100 */
101 public function set_plugin_options( string $plugin, array $options = array() ) {
102 if ( empty( $plugin ) ) {
103 return;
104 }
105
106 $options = wp_parse_args(
107 $options,
108 array(
109 'screens' => array(),
110 'extra_args' => array(),
111 )
112 );
113
114 // Set options.
115 $this->set_plugin_screens( $plugin, $options['screens'] );
116 $this->set_plugin_extra_args( $plugin, $options['extra_args'] );
117 }
118
119 /**
120 * Set admin screens to process.
121 *
122 * UI will be rendered only on these screens.
123 *
124 * @since 1.0.0
125 *
126 * @param string $plugin Plugin identifier.
127 * @param array $screens Screen IDs.
128 *
129 * @return void
130 */
131 public function set_plugin_screens( string $plugin, array $screens = array() ) {
132 if ( ! empty( $screens ) ) {
133 foreach ( $screens as $screen ) {
134 $this->screens[ $screen ] = $plugin;
135 }
136 }
137 }
138
139 /**
140 * Set extra query arguments for login and register redirects.
141 *
142 * @since 1.0.0
143 *
144 * @param string $plugin Plugin identifier.
145 * @param array $args Extra arguments.
146 *
147 * @return void
148 */
149 public function set_plugin_extra_args( string $plugin, array $args = array() ) {
150 $this->extra_args[ $plugin ] = $args;
151 }
152
153 /**
154 * Make sure we get WDP ID in plugins data.
155 *
156 * NOTE: We NEED TO keep using WDP ID as the
157 * key, because that's how the WP filter works.
158 *
159 * @since 1.0.0
160 *
161 * @param array $headers Existing headers.
162 *
163 * @return array
164 */
165 public function include_wdp_id_header( $headers ) {
166 // Include WDP ID.
167 $headers[] = 'WDP ID';
168
169 return $headers;
170 }
171
172 /**
173 * Get plugin identifier.
174 *
175 * Get an identifier for plugins to identify if the
176 * module was initialised by their plugin.
177 * If no identifier was being passed from plugin, it will have
178 * a default value "default".
179 *
180 * @since 1.0.0
181 *
182 * @return string
183 */
184 public function get_plugin_id() {
185 return $this->plugin;
186 }
187
188 /**
189 * Render hub connector UI.
190 *
191 * @since 1.0.0
192 *
193 * @param string $plugin Plugin identifier.
194 */
195 public function render( $plugin = 'default' ) {
196 if ( ! empty( $plugin ) ) {
197 $this->plugin = $plugin;
198 }
199
200 echo '<div id="wpmudev-hub-connector"></div>';
201
202 // Enqueue assets.
203 $this->enqueue_assets();
204 }
205
206 /**
207 * Add required classes to HTML body.
208 *
209 * @since 1.0.0
210 *
211 * @param string $classes Class names.
212 */
213 public function add_body_class( $classes ) {
214 if ( $this->is_allowed_screen() ) {
215 $classes .= ' ' . \WPMUDEV_HUB_CONNECTOR_SUI_VERSION;
216 }
217
218 return $classes;
219 }
220
221 /**
222 * Check if current admin page is one of the allowed pages.
223 *
224 * @since 1.0.0
225 *
226 * @return bool
227 */
228 public function is_allowed_screen() {
229 if ( function_exists( 'get_current_screen' ) ) {
230 $screen = get_current_screen();
231 // Allowed screens.
232 $screens = array_keys( $this->screens );
233
234 // Check if current screen found in list.
235 return ! empty( $screen->id ) && in_array( $screen->id, $screens, true );
236 }
237
238 return false;
239 }
240
241 /**
242 * Enqueue styles and scripts for UI.
243 *
244 * This should be called only when required.
245 *
246 * @since 1.0.0
247 */
248 protected function enqueue_assets() {
249 // SUI and custom styles.
250 wp_enqueue_style(
251 'hub-connector',
252 plugin_dir_url( \WPMUDEV_HUB_CONNECTOR_FILE ) . 'assets/css/hub-connector.min.css',
253 array(),
254 \WPMUDEV_HUB_CONNECTOR_VERSION
255 );
256
257 // Script.
258 wp_enqueue_script(
259 'hub-connector',
260 plugin_dir_url( \WPMUDEV_HUB_CONNECTOR_FILE ) . 'assets/js/hub-connector.min.js',
261 array( 'wp-element', 'wp-i18n' ),
262 \WPMUDEV_HUB_CONNECTOR_VERSION,
263 true
264 );
265
266 // Get current URL.
267 $current_url = remove_query_arg(
268 array(
269 'api_error',
270 'set_apikey',
271 'auth_error',
272 'connection_error',
273 'invalid_key',
274 'site_limit_exceeded',
275 'site_limit',
276 'available_hosting_sites',
277 'hub_connector_callback',
278 'is_multi_auth',
279 'user_apikey',
280 'auth_nonce',
281 'type',
282 ),
283 Data::get()->current_url()
284 );
285
286 // Prepare redirect URL.
287 $redirect_url = add_query_arg( array( 'hub_connector_callback' => 1 ), $current_url );
288
289 $auth_nonce = wp_create_nonce( 'auth_nonce' );
290
291 // Extra arguments.
292 $extra_args = $this->get_plugin_extra_args_from_screen();
293
294 // URL arguments for registration URL.
295 $register_args = array(
296 'signup' => 'site-connect',
297 'site_connect_url' => rawurlencode( add_query_arg( 'auth_nonce', $auth_nonce, $redirect_url ) ),
298 );
299 // Include extra args.
300 if ( ! empty( $extra_args['register'] ) ) {
301 $register_args = array_merge( $register_args, $extra_args['register'] );
302 }
303
304 // Vars for javascript.
305 $vars = array(
306 'api_url' => rest_url( 'hub-connector/v1/' ),
307 'nonce' => wp_create_nonce( 'wp_rest' ),
308 'is_syncing' => false,
309 'is_team_selection' => false,
310 'has_access' => current_user_can( 'manage_options' ),
311 'is_logged_in' => API::get()->is_logged_in(),
312 // Not being used for literal output / DB insert.
313 // phpcs:disable WordPress.Security.NonceVerification.Recommended
314 'current_tab' => isset( $_GET['hub_connector_callback'] ) ? 'login' : 'register',
315 // phpcs:enable WordPress.Security.NonceVerification.Recommended
316 'login' => array(
317 'hub_auth_url' => add_query_arg(
318 $extra_args['auth'] ?? array(),
319 API::get()->rest_url( 'site-authenticate' )
320 ),
321 'team_auth_url' => add_query_arg(
322 $extra_args['team_auth'] ?? array(),
323 API::get()->rest_url( 'site-authenticate-team' )
324 ),
325 'google_auth_url' => add_query_arg(
326 $extra_args['google_auth'] ?? array(),
327 API::get()->rest_url( 'google-auth' )
328 ),
329 'redirect_url' => $redirect_url,
330 'domain' => Data::get()->network_site_url(),
331 'auth_nonce' => $auth_nonce,
332 'current_url' => $current_url,
333 'forgot_url' => add_query_arg(
334 $extra_args['forgot_password'] ?? array(),
335 Data::get()->server_url( 'forgot-password' )
336 ),
337 'register_url' => add_query_arg(
338 $register_args,
339 Data::get()->server_url( 'register' )
340 ),
341 ),
342 'texts' => $this->text_vars(),
343 );
344
345 /**
346 * Filter hook to modify script vars based on actions.
347 *
348 * @since 1.0.0
349 *
350 * @param array $vars Vars.
351 * @param string $plugin Plugin identifier.
352 */
353 $vars = apply_filters( 'wpmudev_hub_connector_localize_vars', $vars, $this->get_plugin_id() );
354
355 // Localized vars.
356 wp_localize_script( 'hub-connector', 'hubConnectorVars', $vars );
357 }
358
359 /**
360 * Process auth callback from the API.
361 *
362 * This method handles the dynamic script vars required
363 * for the UI.
364 *
365 * @since 1.0.0
366 *
367 * @return bool
368 */
369 public function process_auth_callback() {
370 // we do verify nonce, but we do have more sanity checks to be done first.
371 // phpcs:disable WordPress.Security.NonceVerification.Recommended
372
373 // Only for Hub connector callback.
374 if ( ! isset( $_REQUEST['hub_connector_callback'] ) ) {
375 return false;
376 }
377
378 // Should be capable to perform the actions.
379 if ( ! current_user_can( 'manage_options' ) ) {
380 return $this->update_vars( array( 'has_access' => false ) );
381 }
382
383 // If not logged in.
384 if ( ! API::get()->is_logged_in() ) {
385 // Auth nonce verification.
386 if ( ! $this->verify_nonce() ) {
387 // Failed. So no access.
388 return $this->update_vars( array( 'has_access' => false ) );
389 }
390
391 $error = $this->get_auth_error();
392 if ( ! empty( $error ) ) {
393 // Set auth errors.
394 return $this->update_vars( array( 'auth_error' => $error ) );
395 }
396
397 // Is team selection callback.
398 if ( $this->is_team_selection() ) {
399 $user_api_key = trim( sanitize_key( wp_unslash( $_REQUEST['user_apikey'] ?? '' ) ) );
400 // Get the teams for API key.
401 $teams = API::get()->get_hub_teams( $user_api_key );
402
403 // Set team selection page vars.
404 return $this->update_vars(
405 array(
406 'api_key' => $user_api_key,
407 'hub_teams' => $teams,
408 'is_team_selection' => true,
409 )
410 );
411 }
412
413 // Is the set API key page.
414 if ( ! empty( $_REQUEST['set_apikey'] ) ) {
415 $set_api_key = trim( sanitize_key( wp_unslash( $_REQUEST['set_apikey'] ) ) );
416 // Set API key.
417 API::get()->set_api_key( $set_api_key );
418
419 // Make sure to start syncing.
420 return $this->update_vars(
421 array(
422 'is_syncing' => true,
423 'is_logged_in' => true,
424 )
425 );
426 }
427 }
428
429 // phpcs:enable WordPress.Security.NonceVerification.Recommended
430
431 return false;
432 }
433
434 /**
435 * Get authentication error messages.
436 *
437 * Based on the error code, prepare different error messages.
438 *
439 * @since 1.0.0
440 *
441 * @return string
442 */
443 private function get_auth_error() {
444 /**
445 * Nonce is verified in `process_auth_callback` before this method called.
446 *
447 * @see self::process_auth_callback()
448 */
449 // phpcs:disable WordPress.Security.NonceVerification.Recommended
450
451 $error = '';
452 $reset_url = Data::get()->server_url( 'wp-login.php?action=lostpassword' );
453 $skip_trial_url = Data::get()->server_url( 'hub/account/?skip_trial' );
454 $trial_info_url = Data::get()->server_url( 'docs/getting-started/how-free-trials-work/' );
455 $websites_url = Data::get()->server_url( 'hub2/' );
456 $security_info_url = Data::get()->server_url( 'manuals/hub-security/' );
457 $support_url = Data::get()->server_url( 'hub/support/' );
458 $account_details_url = Data::get()->server_url( 'hub2/account/details/' );
459
460 if ( isset( $_GET['api_error'] ) ) {
461 // Get errors.
462 $api_error = sanitize_key( wp_unslash( $_GET['api_error'] ) );
463 $auth_error = sanitize_key( wp_unslash( $_GET['auth_error'] ?? '' ) );
464
465 if ( 1 === (int) $api_error || 'auth' === $api_error ) {
466 switch ( $auth_error ) {
467 case 'google_linked':
468 $error = sprintf(
469 // translators: %s Account detail URL.
470 __(
471 'You are currently using your Google account as your preferred login method. If you wish to login with your WPMU DEV email & password instead, please change the <strong>Login Method</strong> in <a href="%s" target="_blank">your WPMU DEV account</a>.',
472 'wpmudev'
473 ),
474 $account_details_url
475 );
476 break;
477 case 'google_unlinked':
478 $error = sprintf(
479 // translators: %s Account detail URL.
480 __(
481 'You are currently using your WPMU DEV email & password as your preferred login method. If you wish to login with your Google account instead, please change the <strong>Login Method</strong> in <a href="%s" target="_blank">your WPMU DEV account</a>.',
482 'wpmudev'
483 ),
484 $account_details_url
485 );
486 break;
487 case 'reauth_google':
488 $error = sprintf(
489 // translators: %1$s Account detail URL, %2$s Reset URL.
490 __(
491 'Due to security improvements, you will need to re-link your Google account in the Hub. Please log in with your WPMU DEV email & password for now, then set up your preferred <strong>Login Method</strong> in <a href="%1$s" target="_blank">your WPMU DEV account</a>. Forgot your password? You can <a href="%2$s" target="_blank">reset it here</a>.',
492 'wpmudev'
493 ),
494 $account_details_url,
495 $reset_url
496 );
497 break;
498 default:
499 // Invalid credentials.
500 $error = sprintf(
501 '%s<br><a href="%s" target="_blank">%s</a>',
502 esc_html__( 'Your login details were incorrect. Please make sure you\'re using your WPMU DEV email and password and try again.', 'wpmudev' ),
503 $reset_url,
504 esc_html__( 'Forgot your password?', 'wpmudev' )
505 );
506 break;
507 }
508 } else {
509 switch ( $api_error ) {
510 case 'in_trial':
511 $error = sprintf(
512 '%s<br><a href="%s" target="_blank">%s</a>',
513 sprintf(
514 // translators: %1$s Rest URL, %2$s Upgrade URL, %3$s Trial URL.
515 __(
516 'This domain has previously been registered with us by the user %1$s. To use WPMU DEV on this domain, you can either log in with the original account (you can <a target="_blank" href="%2$s">reset your password</a>) or <a target="_blank" href="%3$s">upgrade your trial</a> to a full membership. Trial accounts can\'t use previously registered domains - <a target="_blank" href="%4$s">here\'s why</a>.',
517 'wpmudev'
518 ),
519 '<strong style="word-break: break-all;">' . esc_html( $_GET['display_name'] ) . '</strong>', // phpcs:ignore
520 $reset_url,
521 $skip_trial_url,
522 $trial_info_url
523 ),
524 $support_url,
525 __( 'Contact support if you need further assistance &raquo;', 'wpmudev' )
526 );
527 break;
528 case 'already_registered':
529 $error = sprintf(
530 // translators: %1$d Account name, %2$s Security info, %3$s Hub URL, %4$s Support URL.
531 __(
532 'This site is currently registered to %1$s. For <a target="_blank" href="%2$s">security reasons</a> they will need to go to the <a target="_blank" href="%3$s">WPMU DEV Hub</a> and remove this domain before you can log in. If you do not have access to that account, and have no way of contacting that user, please <a target="_blank" href="%4$s">contact support for assistance</a>.',
533 'wpmudev'
534 ),
535 '<strong style="word-break: break-all;">' . esc_html( $_GET['display_name'] ) . '</strong>', // phpcs:ignore.
536 $security_info_url,
537 $websites_url,
538 $support_url
539 );
540 break;
541 case 'banned_account':
542 $error = sprintf(
543 // translators: %s Support URL.
544 __( 'This domain cannot be registered to your WPMU DEV account.<br><a href="%s">Contact Accounts & Billing if you need further assistance »</a>', 'wpmudev' ),
545 Data::get()->server_url( 'hub2/#ask-question' )
546 );
547 break;
548 case 'invalid_nonce':
549 case 'invalid_double_submit_cookie':
550 case 'invalid_google_creds':
551 case '':
552 $error = __( 'Google login failed. Please try again.', 'wpmudev' );
553 break;
554 default:
555 // This in case we add new error types in the future.
556 $error = __( 'Unknown error. Please update the WPMU DEV Dashboard plugin and try again.', 'wpmudev' );
557 break;
558 }
559 }
560 } elseif ( ! empty( $_REQUEST['connection_error'] ) ) {
561 // Variable `$connection_error` is set by the UI function `render_dashboard`.
562 $error = sprintf(
563 '%s<br>%s<br><em>%s</em>',
564 __( 'Your server had a problem connecting to WPMU DEV. Please try again.', 'wpmudev' ),
565 __( 'If this problem continues, please contact your host with this error message and ask:', 'wpmudev' ),
566 sprintf(
567 // translators: url to API.
568 __( '"Is PHP on my server properly configured to be able to contact %s with a POST HTTP request via fsockopen or CURL?"', 'wpmudev' ),
569 Data::get()->server_url()
570 )
571 );
572 } elseif ( ! empty( $_REQUEST['invalid_key'] ) ) {
573 // Invalid API key.
574 $error = __( 'Your API Key was invalid. Please try again.', 'wpmudev' );
575 }
576
577 /**
578 * Filter to modify auth error text.
579 *
580 * @since 1.0.0
581 *
582 * @param string $error Error message.
583 * @param string $plugin Plugin identifier.
584 */
585 return apply_filters( 'wpmudev_hub_connector_get_auth_error', $error, $this->get_plugin_id() );
586 // phpcs:enable WordPress.Security.NonceVerification.Recommended
587 }
588
589 /**
590 * Check if current request is team selection after auth.
591 *
592 * @since 1.0.0
593 *
594 * @return bool
595 */
596 private function is_team_selection() {
597 /**
598 * Nonce is verified in `process_auth_callback` before this method called.
599 *
600 * @see self::process_auth_callback()
601 */
602 // phpcs:disable WordPress.Security.NonceVerification.Recommended
603 return (
604 // Should have multi auth param.
605 isset( $_REQUEST['is_multi_auth'] )
606 && 1 === (int) $_REQUEST['is_multi_auth']
607 // Should have an API key.
608 && ! empty( $_REQUEST['user_apikey'] )
609 );
610
611 // phpcs:enable WordPress.Security.NonceVerification.Recommended
612 }
613
614 /**
615 * Verify auth nonce.
616 *
617 * After or during the hub auth process, we need to make sure
618 * the nonce is valid.
619 *
620 * @since 1.0.0
621 *
622 * @return bool
623 */
624 private function verify_nonce() {
625 return wp_verify_nonce( ( sanitize_text_field( wp_unslash( $_REQUEST['auth_nonce'] ?? '' ) ) ), 'auth_nonce' );
626 }
627
628 /**
629 * Script vars to assign.
630 *
631 * @since 1.0.0
632 *
633 * @param array $data Script vars.
634 *
635 * @return bool
636 */
637 private function update_vars( $data ) {
638 // Update the script vars array.
639 add_filter(
640 'wpmudev_hub_connector_localize_vars',
641 function ( $vars ) use ( $data ) {
642 return array_merge( $vars, $data );
643 }
644 );
645
646 return true;
647 }
648
649 /**
650 * Localized text strings for UI.
651 *
652 * @since 1.0.0
653 *
654 * @return array
655 */
656 private function text_vars() {
657 $strings = array(
658 'login_title' => __( 'Let’s connect your site', 'wpmudev' ),
659 'login_desc' => __( 'To manage your site from The Hub, log in with your WPMU DEV account email and password.', 'wpmudev' ),
660 'sync_error' => __( 'Could not sync with Hub. Please try again.', 'wpmudev' ),
661 'sync_desc1' => __( 'The Hub connects WPMU DEV to your website and unlocks all the power of our all-in-one platform services.', 'wpmudev' ),
662 'sync_desc2' => __( 'Once your website is connected to the Hub, you will be able to perform updates, managing services - all from one place.', 'wpmudev' ),
663 'sync_loading' => __( 'Please wait a few moments while we connect your website.', 'wpmudev' ),
664 'team_title' => __( 'Choose The Hub Team', 'wpmudev' ),
665 'team_desc' => __( 'We\'ve noticed that you are a member of multiple teams in The Hub. Which team would you like to connect to this site?', 'wpmudev' ),
666 'team_error' => __( 'Unknown API error occurred. Please try again.', 'wpmudev' ),
667 'create_account_desc' => __( "Create a free account to connect your site to WPMU DEV and activate cloud-based link checker. <i>It's fast, seamless, and free.</i>", 'wpmudev' ),
668 'no_access_desc' => __( 'You do not have the permission to view this page.', 'wpmudev' ),
669 'signin_with_google' => __( 'Sign in with Google', 'wpmudev' ),
670 'forgot_password' => __( 'Forgot password?', 'wpmudev' ),
671 'keep_me_logged_in' => __( 'Keep me logged in', 'wpmudev' ),
672 'login' => __( 'Login', 'wpmudev' ),
673 'new_user' => __( 'I am a new user', 'wpmudev' ),
674 'existing_user' => __( 'I am an existing user', 'wpmudev' ),
675 'continue_with_email' => __( 'OR continue with email', 'wpmudev' ),
676 'create_free_account' => __( 'Create a free account', 'wpmudev' ),
677 );
678
679 /**
680 * Filter hook to modify text string vars.
681 *
682 * @since 1.0.0
683 *
684 * @param array $strings Vars.
685 * @param string $plugin Plugin identifier.
686 */
687 return apply_filters( 'wpmudev_hub_connector_localize_text_vars', $strings, $this->get_plugin_id() );
688 }
689
690 /**
691 * Get extra arguments for plugin.
692 *
693 * @return array
694 */
695 private function get_plugin_extra_args_from_screen(): array {
696 $screen = get_current_screen();
697 // We need screen ID.
698 if ( empty( $screen->id ) ) {
699 return array();
700 }
701
702 $screens = $this->screens;
703 // Current screen is not allowed.
704 if ( ! isset( $screens[ $screen->id ] ) ) {
705 return array();
706 }
707
708 // Get plugin ID.
709 $plugin_id = $screens[ $screen->id ];
710
711 // Get plugin's extra args.
712 return $this->get_plugin_extra_args( $plugin_id );
713 }
714 }
715