PluginProbe
Defender Security – Malware Scanner, Login Security & Firewall / 5.7.2
Defender Security – Malware Scanner, Login Security & Firewall v5.7.2
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-remote.php

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

680 lines 16.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The remote class to perform actions from the Hub.
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 // nature of the callbacks workflow.
14 // phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
15
16 /**
17 * Class Remote
18 */
19 class Remote {
20
21 use Singleton;
22
23 /**
24 * Stores current action being processed
25 *
26 * @var string
27 */
28 protected $current_action = '';
29
30 /**
31 * Stores current action params being processed
32 *
33 * @since 1.0.0
34 * @var array
35 */
36 protected $current_params = array();
37
38 /**
39 * Stores registered remote access actions and their callbacks.
40 *
41 * @var array
42 */
43 protected $actions = array();
44
45 /**
46 * Set up the remote module.
47 *
48 * Here we load and initialize the API request from Hub.
49 *
50 * @since 1.0.0
51 * @access public
52 */
53 public function __construct() {
54 // Only when API key is available.
55 if ( ! API::get()->has_api_key() ) {
56 return;
57 }
58
59 // Using priority because some plugins may initialize updates with low priority.
60 add_action( 'init', array( $this, 'run_request' ), 999 );
61 }
62
63 /**
64 * Setup current request data.
65 *
66 * Set current action name and params to be processed.
67 * If action and params are invalid, we will die with json
68 * error message.
69 *
70 * @since 1.0.0
71 * @access protected
72 *
73 * @return void
74 */
75 public function run_request() {
76 // Do nothing if we don't.
77 if ( ! $this->is_hub_request() ) {
78 return;
79 }
80
81 // Register actions.
82 $this->register_internal_actions();
83 $this->register_plugin_actions();
84
85 // Get the json data.
86 $raw_json = file_get_contents( 'php://input' );
87
88 // Get body.
89 $body = json_decode( $raw_json );
90
91 // Validate hash.
92 $this->validate_request_hash( $_GET['wpmudev-hub'], $raw_json ); // phpcs:ignore
93
94 // Action name is required.
95 if ( ! isset( $body->action ) ) {
96 wp_send_json_error(
97 array(
98 'code' => 'invalid_params',
99 'message' => __( 'The "action" parameter is missing', 'wpmudev' ),
100 )
101 );
102 }
103
104 // Params are required.
105 if ( ! isset( $body->params ) ) {
106 wp_send_json_error(
107 array(
108 'code' => 'invalid_params',
109 'message' => __( 'The "params" object is missing', 'wpmudev' ),
110 )
111 );
112 }
113
114 // Set request data.
115 $this->current_action = $body->action;
116 $this->current_params = $body->params;
117
118 // Now process the action.
119 $this->process_action();
120 }
121
122 /**
123 * Run current request.
124 *
125 * First we will register all actions and then check if current action
126 * is a valid one. If not we will send a json error and die.
127 *
128 * @since 1.0.0
129 *
130 * @return void
131 */
132 private function process_action() {
133 // Continue only if valid action.
134 if ( isset( $this->actions[ $this->current_action ] ) ) {
135 // Execute request action.
136 call_user_func(
137 $this->actions[ $this->current_action ],
138 $this->current_params,
139 $this->current_action,
140 $this
141 );
142
143 // Send success in case the callback didn't respond.
144 wp_send_json_success();
145 } else {
146 // Invalid action.
147 wp_send_json_error(
148 array(
149 'code' => 'unregistered_action',
150 'message' => __( 'This action is not registered. The required plugin is not installed, updated, or configured properly.', 'wpmudev' ),
151 )
152 );
153 }
154 }
155
156 /**
157 * Validate the hash for the request.
158 *
159 * @since 1.0.0
160 *
161 * @param string $hash Hash from header.
162 * @param string $id Request ID.
163 * @param string $json Json string.
164 *
165 * @return bool
166 */
167 public function validate_hash( $hash, $id, $json ) {
168 // Validation.
169 if ( empty( $hash ) || empty( $id ) || empty( $json ) ) {
170 return false;
171 }
172
173 // Get API key.
174 $api_key = API::get()->get_api_key();
175
176 // Combine ID and json string.
177 $hash_string = $id . $json;
178
179 // Generate hash.
180 $new_hash = hash_hmac( 'sha256', $hash_string, $api_key );
181
182 // Timing attack safe string comparison, PHP <5.6 compat added in WP 3.9.2.
183 return hash_equals( $new_hash, $hash );
184 }
185
186 /**
187 * Validate the nonce for the request.
188 *
189 * Check nonce to prevent replay attacks.
190 *
191 * @since 1.0.0
192 * @since 1.0.7 ( add $json body as validation parameter )
193 *
194 * @param string $id Request ID.
195 * @param string $json Request body json raw.
196 *
197 * @return bool
198 */
199 public function validate_nonce( $id, $json ) {
200 // Validation.
201 if ( empty( $id ) ) {
202 return false;
203 }
204
205 if ( ! is_string( $json ) ) {
206 $json = '';
207 }
208
209 // Get nonce from ID.
210 list( $id, $timestamp ) = explode( '-', $id );
211
212 // include the data in the prevention checks, it means identical data / json can not be replayed, but different data / json can be replayed -- which is the whole point of the nonce.
213 $hashed_json = hash_hmac( 'sha256', $json, API::get()->get_api_key() );
214
215 // Get saved nonce.
216 $nonce = floatval( Options::get_transient( sprintf( 'hub_nonce_%s', $hashed_json ) ) );
217
218 if ( floatval( $timestamp ) > $nonce ) {
219 // If valid nonce, save it. Hold for a day ( similar as wp_nonce_tick ).
220 Options::set_transient( sprintf( 'hub_nonce_%s', $hashed_json ), floatval( $timestamp ), DAY_IN_SECONDS );
221
222 return true;
223 }
224
225 return false;
226 }
227
228 /**
229 * Check signature hash of the request.
230 *
231 * @since 4.0.0
232 * @access protected
233 *
234 * @param string $req_id The request id as passed by Hub.
235 * @param string $json The full json body that hash was created on.
236 * @param bool $die_on_failure If set to false the function returns a bool.
237 *
238 * @return bool True on success.
239 */
240 protected function validate_request_hash( $req_id, $json, $die_on_failure = true ) {
241 if ( defined( '\WPMUDEV_IS_REMOTE' ) && ! \WPMUDEV_IS_REMOTE ) {
242 if ( $die_on_failure ) {
243 wp_send_json_error(
244 array(
245 'code' => 'remote_disabled',
246 'message' => __( 'Remote calls are disabled in wp-config.php', 'wpmudev' ),
247 )
248 );
249 } else {
250 return false;
251 }
252 }
253
254 if ( empty( $_SERVER['HTTP_WDP_AUTH'] ) ) {
255 if ( $die_on_failure ) {
256 wp_send_json_error(
257 array(
258 'code' => 'missing_auth_header',
259 'message' => __( 'Missing authentication header', 'wpmudev' ),
260 )
261 );
262 } else {
263 return false;
264 }
265 }
266
267 // phpcs:ignore
268 $hash = $_SERVER['HTTP_WDP_AUTH'];
269
270 // Validate auth hash.
271 $is_valid = $this->validate_hash( $hash, $req_id, $json );
272
273 if ( ! $is_valid && $die_on_failure ) {
274 wp_send_json_error(
275 array(
276 'code' => 'incorrect_auth',
277 'message' => __( 'Incorrect authentication', 'wpmudev' ),
278 )
279 );
280 }
281
282 // Check nonce to prevent replay attacks.
283 if ( ! $this->validate_nonce( $req_id, $json ) ) {
284 if ( $die_on_failure ) {
285 wp_send_json_error(
286 array(
287 'code' => 'nonce_failed',
288 'message' => __( 'Nonce check failed', 'wpmudev' ),
289 )
290 );
291 } else {
292 return false;
293 }
294 }
295
296 if ( ! defined( '\WPMUDEV_IS_REMOTE' ) ) {
297 define( 'WPMUDEV_IS_REMOTE', $is_valid );
298 }
299
300 return $is_valid;
301 }
302
303 /**
304 * Registers a Hub api action and callback for it.
305 *
306 * @since 1.0.0
307 *
308 * @param string $action Action name.
309 * @param callable $callback The name of the function you wish to be called.
310 *
311 * @return void
312 */
313 public function register_action( $action, $callback ) {
314 $this->actions[ $action ] = $callback;
315 }
316
317 /**
318 * Logout of this site, removing it from the Hub.
319 *
320 * @since 1.0.0
321 *
322 * @param object $params Parameters passed in json body.
323 * @param string $action The action name that was called.
324 *
325 * @return void
326 */
327 public function action_logout( $params, $action ) {
328 // Logout the site.
329 API::get()->logout( false );
330
331 wp_send_json_success();
332 }
333
334 /**
335 * Installs a list of plugins and themes by pid or slug.
336 *
337 * Handles multiple, but should normally be called with
338 * only one package at a time.
339 *
340 * @since 1.0.0
341 *
342 * @param object $params Parameters passed in json body.
343 * @param string $action The action name that was called.
344 *
345 * @return void
346 */
347 public function action_install( $params, $action ) {
348 $errors = array();
349 $installed = array();
350
351 // Set options.
352 $options = array(
353 // Activation is available only for plugins.
354 'activate' => ! empty( $params->is_activate ),
355 // Overwrite if folder already exists.
356 'overwrite' => ! isset( $params->overwrite ) || (bool) $params->overwrite,
357 );
358
359 // Process plugins.
360 if ( isset( $params->plugins ) && is_array( $params->plugins ) ) {
361 foreach ( $params->plugins as $plugin ) {
362 // Perform installation.
363 $success = Upgrader::get()->install( $plugin, 'plugin', $options );
364
365 // If successfully installed.
366 if ( $success ) {
367 $installed[] = array(
368 'file' => $plugin,
369 'log' => array(),
370 );
371 } else {
372 // Set error response.
373 $errors[] = array(
374 'file' => $plugin,
375 'code' => Upgrader::get()->get_error()->get_error_code(),
376 'message' => Upgrader::get()->get_error()->get_error_message(),
377 'log' => array(),
378 );
379 }
380 }
381 }
382
383 // Process themes.
384 if ( isset( $params->themes ) && is_array( $params->themes ) ) {
385 foreach ( $params->themes as $theme ) {
386 // Perform installation.
387 $success = Upgrader::get()->install( $theme, 'theme', $options );
388
389 // Prepare success response.
390 if ( $success ) {
391 $installed[] = array(
392 'file' => $theme,
393 'log' => array(),
394 );
395 } else {
396 // Prepare error response.
397 $errors[] = array(
398 'file' => $theme,
399 'code' => Upgrader::get()->get_error()->get_error_code(),
400 'message' => Upgrader::get()->get_error()->get_error_message(),
401 'log' => array(),
402 );
403 }
404 }
405 }
406
407 if ( count( $installed ) ) {
408 // If at least one project installed.
409 wp_send_json_success( compact( 'installed', 'errors' ) );
410 } else {
411 // Errors only :(.
412 wp_send_json_error( compact( 'installed', 'errors' ) );
413 }
414 }
415
416 /**
417 * Activates a list of plugins and themes by slug.
418 *
419 * Handles multiple, but should normally be called with only one package at a time.
420 *
421 * @since 1.0.0
422 *
423 * @param object $params Parameters passed in json body.
424 * @param string $action The action name that was called.
425 *
426 * @return void
427 */
428 public function action_activate( $params, $action ) {
429 include_once ABSPATH . 'wp-admin/includes/plugin.php';
430
431 $errors = array();
432 $activated = array();
433
434 // Process plugins.
435 if ( isset( $params->plugins ) && is_array( $params->plugins ) ) {
436 foreach ( $params->plugins as $plugin ) {
437 // This checks if it's valid already.
438 $result = activate_plugin( $plugin, '', is_multisite() );
439 if ( is_wp_error( $result ) ) {
440 $errors[] = array(
441 'file' => $plugin,
442 'code' => $result->get_error_code(),
443 'message' => $result->get_error_message(),
444 );
445 } else {
446 // Do Hub sync.
447 Actions::get()->set_shutdown_sync();
448
449 $activated[] = array( 'file' => $plugin );
450 }
451 }
452 }
453
454 // Process themes.
455 if ( isset( $params->themes ) && is_array( $params->themes ) ) {
456 foreach ( $params->themes as $theme ) {
457 // Check that this is a valid theme.
458 $check_theme = wp_get_theme( $theme );
459
460 if ( ! $check_theme->exists() ) {
461 $errors[] = array(
462 'file' => $theme,
463 'code' => $check_theme->errors()->get_error_code(),
464 'message' => $check_theme->errors()->get_error_message(),
465 );
466 continue;
467 }
468
469 if ( is_multisite() ) {
470 // Allow theme network wide.
471 $allowed_themes = get_site_option( 'allowedthemes' );
472 $allowed_themes[ $theme ] = true;
473 update_site_option( 'allowedthemes', $allowed_themes );
474 } else {
475 switch_theme( $theme );
476 }
477
478 // Do Hub sync.
479 Actions::get()->set_shutdown_sync();
480
481 $activated[] = array( 'file' => $theme );
482 }
483 }
484
485 if ( count( $activated ) ) {
486 wp_send_json_success( compact( 'activated', 'errors' ) );
487 } else {
488 wp_send_json_error( compact( 'activated', 'errors' ) );
489 }
490 }
491
492 /**
493 * Deactivates a list of plugins and themes by pid or slug.
494 *
495 * Handles multiple, but should normally be called with only one package at a time.
496 *
497 * @since 1.0.0
498 *
499 * @param object $params Parameters passed in json body.
500 * @param string $action The action name that was called.
501 *
502 * @return void
503 */
504 public function action_deactivate( $params, $action ) {
505 include_once ABSPATH . 'wp-admin/includes/plugin.php';
506
507 $errors = array();
508 $deactivated = array();
509
510 // Process plugins.
511 if ( isset( $params->plugins ) && is_array( $params->plugins ) ) {
512 foreach ( $params->plugins as $plugin ) {
513 // Check that it's a valid plugin.
514 $valid = validate_plugin( $plugin );
515 if ( is_wp_error( $valid ) ) {
516 $errors[] = array(
517 'file' => $plugin,
518 'code' => $valid->get_error_code(),
519 'message' => $valid->get_error_message(),
520 );
521 continue;
522 }
523
524 deactivate_plugins( $plugin, false, is_multisite() );
525
526 // Do hub sync.
527 Actions::get()->set_shutdown_sync();
528
529 $deactivated[] = array( 'file' => $plugin );
530 }
531 }
532
533 // Process themes.
534 if ( isset( $params->themes ) && is_array( $params->themes ) ) {
535 foreach ( $params->themes as $theme ) {
536 // Check that this is a valid theme.
537 $check_theme = wp_get_theme( $theme );
538 if ( ! $check_theme->exists() ) {
539 $errors[] = array(
540 'file' => $theme,
541 'code' => $check_theme->errors()->get_error_code(),
542 'message' => $check_theme->errors()->get_error_message(),
543 );
544 continue;
545 }
546
547 if ( is_multisite() ) {
548 // Disallow theme network wide.
549 $allowed_themes = get_site_option( 'allowedthemes' );
550 unset( $allowed_themes[ $theme ] );
551 update_site_option( 'allowedthemes', $allowed_themes );
552
553 // Do hub sync.
554 Actions::get()->set_shutdown_sync();
555
556 $deactivated[] = array( 'file' => $theme );
557 }
558 }
559 }
560
561 if ( count( $deactivated ) ) {
562 wp_send_json_success( compact( 'deactivated', 'errors' ) );
563 } else {
564 wp_send_json_error( compact( 'deactivated', 'errors' ) );
565 }
566 }
567
568 /**
569 * Get a list of registered Hub actions that can be called.
570 *
571 * @since 1.0.0
572 *
573 * @param object $params Parameters passed in json body.
574 * @param string $action The action name that was called.
575 *
576 * @return void
577 */
578 public function action_registered( $params, $action ) {
579 $actions = $this->actions;
580
581 // Make class names human-readable.
582 foreach ( $actions as $action => $callback ) {
583 if ( is_array( $callback ) ) {
584 $actions[ $action ] = array( get_class( $callback[0] ), $callback[1] );
585 } elseif ( is_object( $callback ) ) {
586 $actions[ $action ] = 'Closure';
587 } else {
588 $actions[ $action ] = trim( $callback ); // Cleans up lambda function names.
589 }
590 }
591
592 wp_send_json_success( $actions );
593 }
594
595 /**
596 * Register actions that are used by the Dashboard plugin.
597 *
598 * These are the internal actions which act as API endpoints
599 * between Dash plugin and Hub for communication.
600 *
601 * @since 1.0.0
602 *
603 * @return void
604 */
605 protected function register_internal_actions() {
606 $actions = array(
607 'registered_actions' => 'action_registered',
608 'logout' => 'action_logout',
609 'activate' => 'action_activate',
610 'deactivate' => 'action_deactivate',
611 'install' => 'action_install',
612 );
613
614 foreach ( $actions as $action => $callback ) {
615 // Register action.
616 $this->register_action( $action, array( $this, $callback ) );
617 }
618 }
619
620 /**
621 * Registers custom Hub actions from other DEV plugins
622 *
623 * Other plugins should use the wdp_register_hub_action
624 * filter to add an item to the associative array as
625 * 'action_name' => 'callback'
626 *
627 * @since 1.0.0
628 *
629 * @return void
630 */
631 protected function register_plugin_actions() {
632 /**
633 * Registers a Hub api action and callback for it.
634 *
635 * Support for existing hooks for Dash plugin.
636 *
637 * @since 1.0.0
638 *
639 * @param string $action Action name.
640 * @param callable $callback The name of the function you wish to be called.
641 */
642 $actions = apply_filters( 'wdp_register_hub_action', array() );
643
644 /**
645 * Registers a Hub api action and callback for it.
646 *
647 * @since 1.0.0
648 *
649 * @param string $action Action name.
650 * @param callable $callback The name of the function you wish to be called.
651 */
652 $actions = apply_filters( 'wpmudev_hub_connector_register_action', $actions );
653
654 foreach ( $actions as $action => $callback ) {
655 // Check action is not already registered and valid.
656 if ( ! isset( $this->actions[ $action ] ) && is_callable( $callback ) ) {
657 $this->register_action( $action, $callback );
658 }
659 }
660 }
661
662 /**
663 * Check if current request is from Hub.
664 *
665 * Currently, we need this class only for API requests from
666 * Hub. So checking for a 'wpmudev-hub' param is useful to
667 * identify the request.
668 *
669 * @since 1.0.0
670 * @access protected
671 *
672 * @return bool
673 */
674 protected function is_hub_request() {
675 return ! empty( $_GET['wpmudev-hub'] ); // phpcs:ignore
676 }
677 }
678
679 // phpcs:enable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
680