222 lines
5.8 KiB
PHP
222 lines
5.8 KiB
PHP
<?php
|
|
/**
|
|
* The Jetpack Connection Rest Authentication file.
|
|
*
|
|
* @package automattic/jetpack-connection
|
|
*/
|
|
|
|
namespace Automattic\Jetpack\Connection;
|
|
|
|
use WP_Error;
|
|
|
|
/**
|
|
* The Jetpack Connection Rest Authentication class.
|
|
*/
|
|
class Rest_Authentication {
|
|
|
|
/**
|
|
* The rest authentication status.
|
|
*
|
|
* @since 1.17.0
|
|
* @var boolean
|
|
*/
|
|
private $rest_authentication_status = null;
|
|
|
|
/**
|
|
* The rest authentication type.
|
|
* Can be either 'user' or 'blog' depending on whether the request
|
|
* is signed with a user or a blog token.
|
|
*
|
|
* @since 1.29.0
|
|
* @var string
|
|
*/
|
|
private $rest_authentication_type = null;
|
|
|
|
/**
|
|
* The Manager object.
|
|
*
|
|
* @since 1.17.0
|
|
* @var Object
|
|
*/
|
|
private $connection_manager = null;
|
|
|
|
/**
|
|
* Holds the singleton instance of this class
|
|
*
|
|
* @since 1.17.0
|
|
* @var Object
|
|
*/
|
|
private static $instance = false;
|
|
|
|
/**
|
|
* Flag used to avoid determine_current_user filter to enter an infinite loop
|
|
*
|
|
* @since 1.26.0
|
|
* @var boolean
|
|
*/
|
|
private $doing_determine_current_user_filter = false;
|
|
|
|
/**
|
|
* The constructor.
|
|
*/
|
|
private function __construct() {
|
|
$this->connection_manager = new Manager();
|
|
}
|
|
|
|
/**
|
|
* Controls the single instance of this class.
|
|
*
|
|
* @static
|
|
*/
|
|
public static function init() {
|
|
if ( ! self::$instance ) {
|
|
self::$instance = new self();
|
|
|
|
add_filter( 'determine_current_user', array( self::$instance, 'wp_rest_authenticate' ) );
|
|
add_filter( 'rest_authentication_errors', array( self::$instance, 'wp_rest_authentication_errors' ) );
|
|
}
|
|
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* Authenticates requests from Jetpack server to WP REST API endpoints.
|
|
* Uses the existing XMLRPC request signing implementation.
|
|
*
|
|
* @param int|bool $user User ID if one has been determined, false otherwise.
|
|
*
|
|
* @return int|null The user id or null if the request was authenticated via blog token, or not authenticated at all.
|
|
*/
|
|
public function wp_rest_authenticate( $user ) {
|
|
if ( $this->doing_determine_current_user_filter ) {
|
|
return $user;
|
|
}
|
|
|
|
$this->doing_determine_current_user_filter = true;
|
|
|
|
try {
|
|
if ( ! empty( $user ) ) {
|
|
// Another authentication method is in effect.
|
|
return $user;
|
|
}
|
|
|
|
add_filter(
|
|
'jetpack_constant_default_value',
|
|
__NAMESPACE__ . '\Utils::jetpack_api_constant_filter',
|
|
10,
|
|
2
|
|
);
|
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
if ( ! isset( $_GET['_for'] ) || 'jetpack' !== $_GET['_for'] ) {
|
|
// Nothing to do for this authentication method.
|
|
return null;
|
|
}
|
|
|
|
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
|
if ( ! isset( $_GET['token'] ) && ! isset( $_GET['signature'] ) ) {
|
|
// Nothing to do for this authentication method.
|
|
return null;
|
|
}
|
|
|
|
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) {
|
|
$this->rest_authentication_status = new WP_Error(
|
|
'rest_invalid_request',
|
|
__( 'The request method is missing.', 'jetpack-connection' ),
|
|
array( 'status' => 400 )
|
|
);
|
|
return null;
|
|
}
|
|
|
|
// Only support specific request parameters that have been tested and
|
|
// are known to work with signature verification. A different method
|
|
// can be passed to the WP REST API via the '?_method=' parameter if
|
|
// needed.
|
|
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
|
|
$this->rest_authentication_status = new WP_Error(
|
|
'rest_invalid_request',
|
|
__( 'This request method is not supported.', 'jetpack-connection' ),
|
|
array( 'status' => 400 )
|
|
);
|
|
return null;
|
|
}
|
|
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] && ! empty( file_get_contents( 'php://input' ) ) ) {
|
|
$this->rest_authentication_status = new WP_Error(
|
|
'rest_invalid_request',
|
|
__( 'This request method does not support body parameters.', 'jetpack-connection' ),
|
|
array( 'status' => 400 )
|
|
);
|
|
return null;
|
|
}
|
|
|
|
$verified = $this->connection_manager->verify_xml_rpc_signature();
|
|
|
|
if (
|
|
$verified &&
|
|
isset( $verified['type'] ) &&
|
|
'blog' === $verified['type']
|
|
) {
|
|
// Site-level authentication successful.
|
|
$this->rest_authentication_status = true;
|
|
$this->rest_authentication_type = 'blog';
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
$verified &&
|
|
isset( $verified['type'] ) &&
|
|
'user' === $verified['type'] &&
|
|
! empty( $verified['user_id'] )
|
|
) {
|
|
// User-level authentication successful.
|
|
$this->rest_authentication_status = true;
|
|
$this->rest_authentication_type = 'user';
|
|
return $verified['user_id'];
|
|
}
|
|
|
|
// Something else went wrong. Probably a signature error.
|
|
$this->rest_authentication_status = new WP_Error(
|
|
'rest_invalid_signature',
|
|
__( 'The request is not signed correctly.', 'jetpack-connection' ),
|
|
array( 'status' => 400 )
|
|
);
|
|
return null;
|
|
} finally {
|
|
$this->doing_determine_current_user_filter = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Report authentication status to the WP REST API.
|
|
*
|
|
* @param WP_Error|mixed $value Error from another authentication handler, null if we should handle it, or another value if not.
|
|
* @return WP_Error|boolean|null {@see WP_JSON_Server::check_authentication}
|
|
*/
|
|
public function wp_rest_authentication_errors( $value ) {
|
|
if ( null !== $value ) {
|
|
return $value;
|
|
}
|
|
return $this->rest_authentication_status;
|
|
}
|
|
|
|
/**
|
|
* Resets the saved authentication state in between testing requests.
|
|
*/
|
|
public function reset_saved_auth_state() {
|
|
$this->rest_authentication_status = null;
|
|
$this->connection_manager->reset_saved_auth_state();
|
|
}
|
|
|
|
/**
|
|
* Whether the request was signed with a blog token.
|
|
*
|
|
* @since 1.29.0
|
|
*
|
|
* @return bool True if the request was signed with a valid blog token, false otherwise.
|
|
*/
|
|
public static function is_signed_with_blog_token() {
|
|
$instance = self::init();
|
|
|
|
return true === $instance->rest_authentication_status && 'blog' === $instance->rest_authentication_type;
|
|
}
|
|
}
|