243 lines
7.7 KiB
PHP
243 lines
7.7 KiB
PHP
<?php
|
|
/**
|
|
* The Server_Sandbox class.
|
|
*
|
|
* This feature is only useful for Automattic developers.
|
|
* It configures Jetpack to talk to staging/sandbox servers
|
|
* on WordPress.com instead of production servers.
|
|
*
|
|
* @package automattic/jetpack-sandbox
|
|
*/
|
|
|
|
namespace Automattic\Jetpack\Connection;
|
|
|
|
use Automattic\Jetpack\Constants;
|
|
|
|
/**
|
|
* The Server_Sandbox class.
|
|
*/
|
|
class Server_Sandbox {
|
|
|
|
/**
|
|
* Sets up the action hooks for the server sandbox.
|
|
*/
|
|
public function init() {
|
|
if ( did_action( 'jetpack_server_sandbox_init' ) ) {
|
|
return;
|
|
}
|
|
|
|
add_action( 'requests-requests.before_request', array( $this, 'server_sandbox' ), 10, 4 );
|
|
add_action( 'admin_bar_menu', array( $this, 'admin_bar_add_sandbox_item' ), 999 );
|
|
|
|
/**
|
|
* Fires when the server sandbox is initialized. This action is used to ensure that
|
|
* the server sandbox action hooks are set up only once.
|
|
*
|
|
* @since 1.30.7
|
|
*/
|
|
do_action( 'jetpack_server_sandbox_init' );
|
|
}
|
|
|
|
/**
|
|
* Returns the new url and host values.
|
|
*
|
|
* @param string $sandbox Sandbox domain.
|
|
* @param string $url URL of request about to be made.
|
|
* @param array $headers Headers of request about to be made.
|
|
* @param string $data The body of request about to be made.
|
|
* @param string $method The method of request about to be made.
|
|
*
|
|
* @return array [ 'url' => new URL, 'host' => new Host, 'new_signature => New signature if url was changed ]
|
|
*/
|
|
public function server_sandbox_request_parameters( $sandbox, $url, $headers, $data = null, $method = 'GET' ) {
|
|
$host = '';
|
|
$new_signature = '';
|
|
|
|
if ( ! is_string( $sandbox ) || ! is_string( $url ) ) {
|
|
return array(
|
|
'url' => $url,
|
|
'host' => $host,
|
|
'new_signature' => $new_signature,
|
|
);
|
|
}
|
|
|
|
$url_host = wp_parse_url( $url, PHP_URL_HOST );
|
|
|
|
switch ( $url_host ) {
|
|
case 'public-api.wordpress.com':
|
|
case 'jetpack.wordpress.com':
|
|
case 'jetpack.com':
|
|
case 'dashboard.wordpress.com':
|
|
$host = isset( $headers['Host'] ) ? $headers['Host'] : $url_host;
|
|
$original_url = $url;
|
|
$url = preg_replace(
|
|
'@^(https?://)' . preg_quote( $url_host, '@' ) . '(?=[/?#].*|$)@',
|
|
'${1}' . $sandbox,
|
|
$url,
|
|
1
|
|
);
|
|
|
|
/**
|
|
* Whether to add the X Debug query parameter to the request made to the Sandbox
|
|
*
|
|
* @since 1.36.0
|
|
*
|
|
* @param bool $add_parameter Whether to add the parameter to the request or not. Default is to false.
|
|
* @param string $url The URL of the request being made.
|
|
* @param string $host The host of the request being made.
|
|
*/
|
|
if ( apply_filters( 'jetpack_sandbox_add_profile_parameter', false, $url, $host ) ) {
|
|
$url = add_query_arg( 'XDEBUG_PROFILE', 1, $url );
|
|
|
|
// URL has been modified since the signature was created. We'll need a new one.
|
|
$original_url = add_query_arg( 'XDEBUG_PROFILE', 1, $original_url );
|
|
$new_signature = $this->get_new_signature( $original_url, $headers, $data, $method );
|
|
|
|
}
|
|
}
|
|
|
|
return compact( 'url', 'host', 'new_signature' );
|
|
}
|
|
|
|
/**
|
|
* Gets a new signature for the request
|
|
*
|
|
* @param string $url The new URL to be signed.
|
|
* @param array $headers The headers of the request about to be made.
|
|
* @param string $data The body of request about to be made.
|
|
* @param string $method The method of the request about to be made.
|
|
* @return string|null
|
|
*/
|
|
private function get_new_signature( $url, $headers, $data, $method ) {
|
|
|
|
if ( ! empty( $headers['Authorization'] ) ) {
|
|
$a_headers = $this->extract_authorization_headers( $headers );
|
|
if ( ! empty( $a_headers ) ) {
|
|
$token_details = explode( ':', $a_headers['token'] );
|
|
|
|
if ( count( $token_details ) === 3 ) {
|
|
$user_id = $token_details[2];
|
|
$token = ( new Tokens() )->get_access_token( $user_id );
|
|
$time_diff = (int) \Jetpack_Options::get_option( 'time_diff' );
|
|
$jetpack_signature = new \Jetpack_Signature( $token->secret, $time_diff );
|
|
|
|
$signature = $jetpack_signature->sign_request(
|
|
$a_headers['token'],
|
|
$a_headers['timestamp'],
|
|
$a_headers['nonce'],
|
|
$a_headers['body-hash'],
|
|
$method,
|
|
$url,
|
|
$data,
|
|
false
|
|
);
|
|
|
|
if ( $signature && ! is_wp_error( $signature ) ) {
|
|
return $signature;
|
|
} elseif ( is_wp_error( $signature ) ) {
|
|
$this->log_new_signature_error( $signature->get_error_message() );
|
|
}
|
|
} else {
|
|
$this->log_new_signature_error( 'Malformed token on Authorization Header' );
|
|
}
|
|
} else {
|
|
$this->log_new_signature_error( 'Error extracting Authorization Header' );
|
|
}
|
|
} else {
|
|
$this->log_new_signature_error( 'Empty Authorization Header' );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Logs error if the attempt to create a new signature fails
|
|
*
|
|
* @param string $message The error message.
|
|
* @return void
|
|
*/
|
|
private function log_new_signature_error( $message ) {
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
error_log( sprintf( "SANDBOXING: Error re-signing the request. '%s'", $message ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract the values in the Authorization header into an array
|
|
*
|
|
* @param array $headers The headers of the request about to be made.
|
|
* @return array|null
|
|
*/
|
|
public function extract_authorization_headers( $headers ) {
|
|
if ( ! empty( $headers['Authorization'] ) && is_string( $headers['Authorization'] ) ) {
|
|
$header = str_replace( 'X_JETPACK ', '', $headers['Authorization'] );
|
|
$vars = explode( ' ', $header );
|
|
$result = array();
|
|
foreach ( $vars as $var ) {
|
|
$elements = explode( '"', $var );
|
|
if ( count( $elements ) === 3 ) {
|
|
$result[ substr( $elements[0], 0, -1 ) ] = $elements[1];
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Modifies parameters of request in order to send the request to the
|
|
* server specified by `JETPACK__SANDBOX_DOMAIN`.
|
|
*
|
|
* Attached to the `requests-requests.before_request` filter.
|
|
*
|
|
* @param string $url URL of request about to be made.
|
|
* @param array $headers Headers of request about to be made.
|
|
* @param array|string $data Data of request about to be made.
|
|
* @param string $type Type of request about to be made.
|
|
* @return void
|
|
*/
|
|
public function server_sandbox( &$url, &$headers, &$data = null, &$type = null ) {
|
|
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
|
|
return;
|
|
}
|
|
|
|
$original_url = $url;
|
|
|
|
$request_parameters = $this->server_sandbox_request_parameters( Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $url, $headers, $data, $type );
|
|
|
|
$url = $request_parameters['url'];
|
|
|
|
if ( $request_parameters['host'] ) {
|
|
$headers['Host'] = $request_parameters['host'];
|
|
|
|
if ( $request_parameters['new_signature'] ) {
|
|
$headers['Authorization'] = preg_replace( '/signature=\"[^\"]+\"/', 'signature="' . $request_parameters['new_signature'] . '"', $headers['Authorization'] );
|
|
}
|
|
|
|
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
|
error_log( sprintf( "SANDBOXING via '%s': '%s'", Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ), $original_url ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a "Jetpack API Sandboxed" item to the admin bar if the JETPACK__SANDBOX_DOMAIN
|
|
* constant is set.
|
|
*
|
|
* Attached to the `admin_bar_menu` action.
|
|
*
|
|
* @param \WP_Admin_Bar $wp_admin_bar The WP_Admin_Bar instance.
|
|
*/
|
|
public function admin_bar_add_sandbox_item( $wp_admin_bar ) {
|
|
if ( ! Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ) ) {
|
|
return;
|
|
}
|
|
|
|
$node = array(
|
|
'id' => 'jetpack-connection-api-sandbox',
|
|
'title' => 'Jetpack API Sandboxed',
|
|
'meta' => array(
|
|
'title' => 'Sandboxing via ' . Constants::get_constant( 'JETPACK__SANDBOX_DOMAIN' ),
|
|
),
|
|
);
|
|
|
|
$wp_admin_bar->add_menu( $node );
|
|
}
|
|
}
|