Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions _inc/lib/class.core-rest-api-endpoints.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,6 @@ public static function register_endpoints() {
'callback' => __CLASS__ . '::delete_jitm_message'
) );

// Register a site
register_rest_route( 'jetpack/v4', '/verify_registration', array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::verify_registration',
) );

// Authorize a remote user
register_rest_route( 'jetpack/v4', '/remote_authorize', array(
'methods' => WP_REST_Server::EDITABLE,
Expand Down Expand Up @@ -520,28 +514,6 @@ public static function delete_jitm_message( $request ) {
return $jitm->dismiss( $request['id'], $request['feature_class'] );
}

/**
* Handles verification that a site is registered
*
* @since 5.4.0
*
* @param WP_REST_Request $request The request sent to the WP REST API.
*
* @return array|wp-error
*/
public static function verify_registration( $request ) {
require_once JETPACK__PLUGIN_DIR . 'class.jetpack-xmlrpc-server.php';
$xmlrpc_server = new Jetpack_XMLRPC_Server();
$result = $xmlrpc_server->verify_registration( array( $request['secret_1'], $request['state'] ) );

if ( is_a( $result, 'IXR_Error' ) ) {
$result = new WP_Error( $result->code, $result->message );
}

return $result;
}


/**
* Checks if this site has been verified using a service - only 'google' supported at present - and a specfic
* keyring to use to get the token if it is not
Expand Down
9 changes: 0 additions & 9 deletions class.jetpack-xmlrpc-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ function xmlrpc_methods( $core_methods ) {
*/
function bootstrap_xmlrpc_methods() {
return array(
'jetpack.verifyRegistration' => array( $this, 'verify_registration' ),
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
'jetpack.remoteRegister' => array( $this, 'remote_register' ),
);
Expand Down Expand Up @@ -397,14 +396,6 @@ private function tracks_record_error( $name, $error, $user = null ) {
return $error;
}

/**
* Verifies that Jetpack.WordPress.com received a registration request from this site
*/
function verify_registration( $data ) {
// failure modes will be recorded in tracks in the verify_action method
return $this->verify_action( array( 'register', $data[0], $data[1] ) );
}

/**
* @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
*
Expand Down
10 changes: 10 additions & 0 deletions class.jetpack.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*/

use \Automattic\Jetpack\Connection\Manager as Connection_Manager;
use \Automattic\Jetpack\Connection\XMLRPC_Connector as XMLRPC_Connector;
use \Automattic\Jetpack\Connection\REST_Connector as REST_Connector;
use \Automattic\Jetpack\Assets\Logo as Jetpack_Logo;

require_once( JETPACK__PLUGIN_DIR . '_inc/lib/class.media.php' );
Expand Down Expand Up @@ -599,6 +601,8 @@ private function __construct() {
add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'authorize_xmlrpc_methods' ) );
}
} else {
new XMLRPC_Connector( $this->connection_manager );

// The bootstrap API methods.
add_filter( 'xmlrpc_methods', array( $this->xmlrpc_server, 'bootstrap_xmlrpc_methods' ) );
$signed = $this->verify_xml_rpc_signature();
Expand All @@ -623,6 +627,8 @@ private function __construct() {
if ( Jetpack::is_active() ) {
add_action( 'login_form_jetpack_json_api_authorization', array( &$this, 'login_form_json_api_authorization' ) );
add_filter( 'xmlrpc_methods', array( $this, 'public_xmlrpc_methods' ) );
} else {
add_action( 'rest_api_init', array( $this, 'initialize_rest_api_registration_connector' ) );
}
}

Expand Down Expand Up @@ -730,6 +736,10 @@ private function __construct() {
}
}

function initialize_rest_api_registration_connector() {
new REST_Connector( $this->connection_manager );
}

/**
* This is ported over from the manage module, which has been deprecated and baked in here.
*
Expand Down
134 changes: 125 additions & 9 deletions packages/connection/src/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use Automattic\Jetpack\Connection\Manager_Interface;
use Automattic\Jetpack\Constants;
use Automattic\Jetpack\Tracking\Manager as JetpackTracking;

/**
* The Jetpack Connection Manager class that is used as a single gateway between WordPress.com
Expand Down Expand Up @@ -229,9 +230,122 @@ public function delete_secrets( $action, $user_id ) {
/**
* Responds to a WordPress.com call to register the current site.
* Should be changed to protected.
*
* @param array $registration_data Array of [ secret_1, user_id ].
*/
public function handle_registration() {
public function handle_registration( array $registration_data ) {
list( $registration_secret_1, $registration_user_id ) = $registration_data;
if ( empty( $registration_user_id ) ) {
return new \WP_Error( 'registration_state_invalid', __( 'Invalid Registration State', 'jetpack' ), 400 );
}

return $this->verify_secrets( 'register', $registration_secret_1, (int) $registration_user_id );
}

/**
* Verify a Previously Generated Secret.
*
* @param string $action The type of secret to verify.
* @param string $secret_1 The secret string to compare to what is stored.
* @param int $user_id The user ID of the owner of the secret.
*/
protected function verify_secrets( $action, $secret_1, $user_id ) {
$allowed_actions = array( 'register', 'authorize', 'publicize' );
if ( ! in_array( $action, $allowed_actions, true ) ) {
return new \WP_Error( 'unknown_verification_action', 'Unknown Verification Action', 400 );
}

$user = get_user_by( 'id', $user_id );

JetpackTracking::record_user_event( "jpc_verify_{$action}_begin", array(), $user );

$return_error = function( \WP_Error $error ) use ( $action, $user ) {
JetpackTracking::record_user_event(
"jpc_verify_{$action}_fail",
array(
'error_code' => $error->get_error_code(),
'error_message' => $error->get_error_message(),
),
$user
);

return $error;
};

$stored_secrets = $this->get_secrets( $action, $user_id );
$this->delete_secrets( $action, $user_id );

if ( empty( $secret_1 ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'secret_1' ),
400
)
);
} elseif ( ! is_string( $secret_1 ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'secret_1' ),
400
)
);
} elseif ( empty( $user_id ) ) {
// $user_id is passed around during registration as "state".
return $return_error(
new \WP_Error(
'state_missing',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is missing.', 'jetpack' ), 'state' ),
400
)
);
} elseif ( ! ctype_digit( (string) $user_id ) ) {
return $return_error(
new \WP_Error(
'verify_secret_1_malformed',
/* translators: "%s" is the name of a paramter. It can be either "secret_1" or "state". */
sprintf( __( 'The required "%s" parameter is malformed.', 'jetpack' ), 'state' ),
400
)
);
}

if ( ! $stored_secrets ) {
return $return_error(
new \WP_Error(
'verify_secrets_missing',
__( 'Verification secrets not found', 'jetpack' ),
400
)
);
} elseif ( is_wp_error( $stored_secrets ) ) {
$stored_secrets->add_data( 400 );
return $return_error( $stored_secrets );
} elseif ( empty( $stored_secrets['secret_1'] ) || empty( $stored_secrets['secret_2'] ) || empty( $stored_secrets['exp'] ) ) {
return $return_error(
new \WP_Error(
'verify_secrets_incomplete',
__( 'Verification secrets are incomplete', 'jetpack' ),
400
)
);
} elseif ( ! hash_equals( $secret_1, $stored_secrets['secret_1'] ) ) {
return $return_error(
new \WP_Error(
'verify_secrets_mismatch',
__( 'Secret mismatch', 'jetpack' ),
400
)
);
}

JetpackTracking::record_user_event( "jpc_verify_{$action}_success", array(), $user );

return $stored_secrets['secret_2'];
}

/**
Expand Down Expand Up @@ -267,11 +381,13 @@ public function disconnect_site() {
}

/**
* @param $text
* The Base64 Encoding of the SHA1 Hash of the Input.
*
* @param string $text The string to hash.
* @return string
*/
function sha1_base64( $text ) {
return base64_encode( sha1( $text, true ) );
public function sha1_base64( $text ) {
return base64_encode( sha1( $text, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
}

/**
Expand All @@ -285,7 +401,7 @@ public function is_usable_domain( $domain ) {

// If it's empty, just fail out.
if ( ! $domain ) {
return new WP_Error(
return new \WP_Error(
'fail_domain_empty',
/* translators: %1$s is a domain name. */
sprintf( __( 'Domain `%1$s` just failed is_usable_domain check as it is empty.', 'jetpack' ), $domain )
Expand Down Expand Up @@ -317,7 +433,7 @@ public function is_usable_domain( $domain ) {
'build.wordpress-develop.test', // VVV pattern.
);
if ( in_array( $domain, $forbidden_domains, true ) ) {
return new WP_Error(
return new \WP_Error(
'fail_domain_forbidden',
sprintf(
/* translators: %1$s is a domain name. */
Expand All @@ -332,7 +448,7 @@ public function is_usable_domain( $domain ) {

// No .test or .local domains.
if ( preg_match( '#\.(test|local)$#i', $domain ) ) {
return new WP_Error(
return new \WP_Error(
'fail_domain_tld',
sprintf(
/* translators: %1$s is a domain name. */
Expand All @@ -347,7 +463,7 @@ public function is_usable_domain( $domain ) {

// No WPCOM subdomains.
if ( preg_match( '#\.WordPress\.com$#i', $domain ) ) {
return new WP_Error(
return new \WP_Error(
'fail_subdomain_wpcom',
sprintf(
/* translators: %1$s is a domain name. */
Expand Down Expand Up @@ -434,7 +550,7 @@ public function get_access_token( $user_id = false, $token_key = false ) {
if ( empty( $user_token_chunks[1] ) || empty( $user_token_chunks[2] ) ) {
return false;
}
if ( $user_id != $user_token_chunks[2] ) {
if ( $user_token_chunks[2] !== (string) $user_id ) {
return false;
}
$possible_normal_tokens[] = "{$user_token_chunks[0]}.{$user_token_chunks[1]}";
Expand Down
4 changes: 3 additions & 1 deletion packages/connection/src/Manager_Interface.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ public function get_secrets( $action, $user_id );
/**
* Responds to a WordPress.com call to register the current site.
* Should be changed to protected.
*
* @param array $registration_data Array of [ secret_1, user_id ].
*/
public function handle_registration();
public function handle_registration( array $registration_data );

/**
* Responds to a WordPress.com call to authorize the current user.
Expand Down
54 changes: 54 additions & 0 deletions packages/connection/src/REST_Connector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Sets up the Connection REST API endpoints.
*
* @package jetpack-connection
*/

namespace Automattic\Jetpack\Connection;

/**
* Registers the REST routes for Connections.
*/
class REST_Connector {
/**
* The Connection Manager.
*
* @var Manager
*/
private $connection;

/**
* Constructor.
*
* @param Manager $connection The Connection Manager.
*/
public function __construct( Manager $connection ) {
$this->connection = $connection;

// Register a site.
register_rest_route(
'jetpack/v4',
'/verify_registration',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'verify_registration' ),
)
);
}

/**
* Handles verification that a site is registered.
*
* @since 5.4.0
*
* @param \WP_REST_Request $request The request sent to the WP REST API.
*
* @return string|WP_Error
*/
public function verify_registration( \WP_REST_Request $request ) {
$registration_data = array( $request['secret_1'], $request['state'] );

return $this->connection->handle_registration( $registration_data );
}
}
Loading