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

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

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