updated plugin Jetpack Protect
version 2.0.0
This commit is contained in:
@ -27,7 +27,12 @@ class Identity_Crisis {
|
||||
/**
|
||||
* Package Version
|
||||
*/
|
||||
const PACKAGE_VERSION = '0.11.1';
|
||||
const PACKAGE_VERSION = '0.15.0';
|
||||
|
||||
/**
|
||||
* Persistent WPCOM blog ID that stays in the options after disconnect.
|
||||
*/
|
||||
const PERSISTENT_BLOG_ID_OPTION_NAME = 'jetpack_persistent_blog_id';
|
||||
|
||||
/**
|
||||
* Instance of the object.
|
||||
@ -87,9 +92,13 @@ class Identity_Crisis {
|
||||
add_filter( 'jetpack_remote_request_url', array( $this, 'add_idc_query_args_to_url' ) );
|
||||
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_secret_to_url_validation_response' ) );
|
||||
add_filter( 'jetpack_connection_validate_urls_for_idc_mitigation_response', array( static::class, 'add_ip_requester_to_url_validation_response' ) );
|
||||
|
||||
add_filter( 'jetpack_options', array( static::class, 'reverse_wpcom_urls_for_idc' ) );
|
||||
|
||||
add_filter( 'jetpack_register_request_body', array( static::class, 'register_request_body' ) );
|
||||
add_action( 'jetpack_site_registered', array( static::class, 'site_registered' ) );
|
||||
|
||||
$urls_in_crisis = self::check_identity_crisis();
|
||||
if ( false === $urls_in_crisis ) {
|
||||
return;
|
||||
@ -113,6 +122,8 @@ class Identity_Crisis {
|
||||
$connection->disconnect_site( false );
|
||||
}
|
||||
|
||||
delete_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
|
||||
// Clear IDC options.
|
||||
self::clear_all_idc_options();
|
||||
}
|
||||
@ -199,10 +210,18 @@ class Identity_Crisis {
|
||||
|| self::validate_sync_error_idc_option() ) {
|
||||
return $url;
|
||||
}
|
||||
$home_url = Urls::home_url();
|
||||
$site_url = Urls::site_url();
|
||||
$hostname = wp_parse_url( $site_url, PHP_URL_HOST );
|
||||
|
||||
// If request is from an IP, make sure ip_requester option is set
|
||||
if ( self::url_is_ip( $hostname ) ) {
|
||||
self::maybe_update_ip_requester( $hostname );
|
||||
}
|
||||
|
||||
$query_args = array(
|
||||
'home' => Urls::home_url(),
|
||||
'siteurl' => Urls::site_url(),
|
||||
'home' => $home_url,
|
||||
'siteurl' => $site_url,
|
||||
);
|
||||
|
||||
if ( self::should_handle_idc() ) {
|
||||
@ -213,6 +232,10 @@ class Identity_Crisis {
|
||||
$query_args['migrate_for_idc'] = true;
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$query_args['multisite'] = true;
|
||||
}
|
||||
|
||||
return add_query_arg( $query_args, $url );
|
||||
}
|
||||
|
||||
@ -1337,4 +1360,153 @@ class Identity_Crisis {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if URL is an IP.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @return bool
|
||||
*/
|
||||
public static function url_is_ip( $hostname = null ) {
|
||||
|
||||
if ( ! $hostname ) {
|
||||
$hostname = wp_parse_url( Urls::site_url(), PHP_URL_HOST );
|
||||
}
|
||||
|
||||
$is_ip = filter_var( $hostname, FILTER_VALIDATE_IP ) !== false ? $hostname : false;
|
||||
return $is_ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add IDC-related data to the registration query.
|
||||
*
|
||||
* @param array $params The existing query params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function register_request_body( array $params ) {
|
||||
$persistent_blog_id = get_option( static::PERSISTENT_BLOG_ID_OPTION_NAME );
|
||||
if ( $persistent_blog_id ) {
|
||||
$params['persistent_blog_id'] = $persistent_blog_id;
|
||||
$params['url_secret'] = URL_Secret::create_secret( 'registration_request_url_secret_failed' );
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the necessary options when site gets registered.
|
||||
*
|
||||
* @param int $blog_id The blog ID.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function site_registered( $blog_id ) {
|
||||
update_option( static::PERSISTENT_BLOG_ID_OPTION_NAME, (int) $blog_id, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to update the ip_requester option.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_update_ip_requester( $hostname ) {
|
||||
// Check if transient exists
|
||||
$transient_key = ip2long( $hostname );
|
||||
if ( $transient_key && ! get_transient( 'jetpack_idc_ip_requester_' . $transient_key ) ) {
|
||||
self::set_ip_requester_for_idc( $hostname, $transient_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If URL is an IP, add the IP value to the ip_requester option with its expiry value.
|
||||
*
|
||||
* @param string $hostname The hostname to check.
|
||||
* @param int $transient_key The transient key.
|
||||
*/
|
||||
public static function set_ip_requester_for_idc( $hostname, $transient_key ) {
|
||||
// Check if option exists
|
||||
$data = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
|
||||
$ip_requester = array(
|
||||
'ip' => $hostname,
|
||||
'expires_at' => time() + 360,
|
||||
);
|
||||
|
||||
// If not set, initialize it
|
||||
if ( empty( $data ) ) {
|
||||
$data = array( $ip_requester );
|
||||
} else {
|
||||
$updated_data = array();
|
||||
$updated_value = false;
|
||||
|
||||
// Remove expired values and update existing IP
|
||||
foreach ( $data as $item ) {
|
||||
if ( time() > $item['expires_at'] ) {
|
||||
continue; // Skip expired IP
|
||||
}
|
||||
|
||||
if ( $item['ip'] === $hostname ) {
|
||||
$item['expires_at'] = time() + 360;
|
||||
$updated_value = true;
|
||||
}
|
||||
|
||||
$updated_data[] = $item;
|
||||
}
|
||||
|
||||
if ( ! $updated_value || empty( $updated_data ) ) {
|
||||
$updated_data[] = $ip_requester;
|
||||
}
|
||||
|
||||
$data = $updated_data;
|
||||
}
|
||||
|
||||
self::update_ip_requester( $data, $transient_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the ip_requester option and set a transient to expire in 5 minutes.
|
||||
*
|
||||
* @param array $data The data to be updated.
|
||||
* @param int $transient_key The transient key.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_ip_requester( $data, $transient_key ) {
|
||||
// Update the option
|
||||
$updated = Jetpack_Options::update_option( 'identity_crisis_ip_requester', $data );
|
||||
// Set a transient to expire in 5 minutes
|
||||
if ( $updated ) {
|
||||
$transient_name = 'jetpack_idc_ip_requester_' . $transient_key;
|
||||
set_transient( $transient_name, $data, 300 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds `ip_requester` to the `jetpack.idcUrlValidation` URL validation endpoint.
|
||||
*
|
||||
* @param array $response The enpoint response that we're modifying.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function add_ip_requester_to_url_validation_response( array $response ) {
|
||||
$requesters = Jetpack_Options::get_option( 'identity_crisis_ip_requester' );
|
||||
if ( $requesters ) {
|
||||
// Loop through the requesters and add the IP to the response if it's not expired
|
||||
$i = 0;
|
||||
foreach ( $requesters as $ip ) {
|
||||
if ( $ip['expires_at'] > time() ) {
|
||||
$response['ip_requester'][] = $ip['ip'];
|
||||
}
|
||||
// Limit the response to five IPs
|
||||
$i = ++$i;
|
||||
if ( $i === 5 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,24 @@ class REST_Endpoints {
|
||||
'permission_callback' => array( static::class, 'url_secret_permission_check' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch URL verification secret.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/identity-crisis/compare-url-secret',
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( static::class, 'compare_url_secret' ),
|
||||
'permission_callback' => array( static::class, 'compare_url_secret_permission_check' ),
|
||||
'args' => array(
|
||||
'secret' => array(
|
||||
'description' => __( 'URL secret to compare to the ones stored in the database.', 'jetpack-idc' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -219,6 +237,31 @@ class REST_Endpoints {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Endpoint for comparing the existing secret.
|
||||
*
|
||||
* @param \WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return WP_Error|\WP_REST_Response
|
||||
*/
|
||||
public static function compare_url_secret( $request ) {
|
||||
$match = false;
|
||||
|
||||
$storage = new URL_Secret();
|
||||
|
||||
if ( $storage->exists() ) {
|
||||
$remote_secret = $request->get_param( 'secret' );
|
||||
$match = $remote_secret && hash_equals( $storage->get_secret(), $remote_secret );
|
||||
}
|
||||
|
||||
return rest_ensure_response(
|
||||
array(
|
||||
'code' => 'success',
|
||||
'match' => $match,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify url_secret create/fetch permissions (valid blog token authentication).
|
||||
*
|
||||
@ -233,4 +276,20 @@ class REST_Endpoints {
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The endpoint is only available on non-connected sites.
|
||||
* use `/identity-crisis/url-secret` for connected sites.
|
||||
*
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function compare_url_secret_permission_check() {
|
||||
return ( new Connection_Manager() )->is_connected()
|
||||
? new WP_Error(
|
||||
'invalid_connection_status',
|
||||
esc_html__( 'The endpoint is not available on connected sites.', 'jetpack-idc' ),
|
||||
array( 'status' => 403 )
|
||||
)
|
||||
: true;
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ class UI {
|
||||
$priority1 = ( array_key_exists( 'priority', $c1 ) && (int) $c1['priority'] ) ? (int) $c1['priority'] : 10;
|
||||
$priority2 = ( array_key_exists( 'priority', $c2 ) && (int) $c2['priority'] ) ? (int) $c2['priority'] : 10;
|
||||
|
||||
return $priority1 > $priority2 ? 1 : -1;
|
||||
return $priority1 <=> $priority2;
|
||||
}
|
||||
);
|
||||
|
||||
@ -165,7 +165,7 @@ class UI {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && 0 === strpos( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
if ( isset( $_SERVER['REQUEST_URI'] ) && str_starts_with( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), $consumer['admin_page'] ) && strlen( $consumer['admin_page'] ) > $consumer_url_length ) {
|
||||
$consumer_chosen = $consumer;
|
||||
$consumer_url_length = strlen( $consumer['admin_page'] );
|
||||
}
|
||||
|
@ -131,4 +131,27 @@ class URL_Secret {
|
||||
private function generate_secret() {
|
||||
return wp_generate_password( 12, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret for response.
|
||||
*
|
||||
* @param string $flow used to tell which flow generated the exception.
|
||||
* @return string
|
||||
*/
|
||||
public static function create_secret( $flow = 'generating_secret_failed' ) {
|
||||
$secret = null;
|
||||
try {
|
||||
|
||||
$secret = new self();
|
||||
$secret->create();
|
||||
|
||||
if ( $secret->exists() ) {
|
||||
$secret = $secret->get_secret();
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
// Track the error and proceed.
|
||||
( new Tracking() )->record_user_event( $flow, array( 'current_url' => Urls::site_url() ) );
|
||||
}
|
||||
return $secret;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user