updated plugin Jetpack Protect
version 4.0.0
This commit is contained in:
@ -101,7 +101,7 @@ class Plan {
|
||||
$products = array_column( Current_Plan::get_products(), 'product_slug' );
|
||||
|
||||
// Check for a plan or product that enables scan.
|
||||
$plan_supports_scan = Current_Plan::supports( 'scan', true );
|
||||
$plan_supports_scan = Current_Plan::supports( 'scan', $force_refresh );
|
||||
$has_scan_product = count( array_intersect( array( 'jetpack_scan', 'jetpack_scan_monthly' ), $products ) ) > 0;
|
||||
$has_scan = $plan_supports_scan || $has_scan_product;
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
/**
|
||||
* Class to handle the Protect Status of Jetpack Protect
|
||||
*
|
||||
* @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @package automattic/jetpack-protect-status
|
||||
*/
|
||||
|
||||
@ -13,7 +15,7 @@ use Automattic\Jetpack\Plugins_Installer;
|
||||
use Automattic\Jetpack\Protect_Models\Extension_Model;
|
||||
use Automattic\Jetpack\Protect_Models\Status_Model;
|
||||
use Automattic\Jetpack\Protect_Models\Threat_Model;
|
||||
use Automattic\Jetpack\Redirect;
|
||||
use Automattic\Jetpack\Protect_Models\Vulnerability_Model;
|
||||
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
@ -109,7 +111,10 @@ class Protect_Status extends Status {
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
self::get_api_url(),
|
||||
'2',
|
||||
array( 'method' => 'GET' ),
|
||||
array(
|
||||
'method' => 'GET',
|
||||
'timeout' => 30,
|
||||
),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
@ -128,6 +133,8 @@ class Protect_Status extends Status {
|
||||
/**
|
||||
* Normalize data from the Protect Report data source.
|
||||
*
|
||||
* @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @param object $report_data Data from the Protect Report.
|
||||
* @return Status_Model
|
||||
*/
|
||||
@ -141,121 +148,193 @@ class Protect_Status extends Status {
|
||||
$status->num_threats = isset( $report_data->num_vulnerabilities ) ? $report_data->num_vulnerabilities : null;
|
||||
$status->num_themes_threats = isset( $report_data->num_themes_vulnerabilities ) ? $report_data->num_themes_vulnerabilities : null;
|
||||
$status->num_plugins_threats = isset( $report_data->num_plugins_vulnerabilities ) ? $report_data->num_plugins_vulnerabilities : null;
|
||||
$status->has_unchecked_items = false;
|
||||
|
||||
// merge plugins from report with all installed plugins before mapping into the Status_Model
|
||||
$installed_plugins = Plugins_Installer::get_plugins();
|
||||
$last_report_plugins = isset( $report_data->plugins ) ? $report_data->plugins : new \stdClass();
|
||||
$status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $last_report_plugins, array( 'type' => 'plugins' ) );
|
||||
// normalize extension information
|
||||
self::normalize_extension_data( $status, $report_data, 'themes' );
|
||||
self::normalize_extension_data( $status, $report_data, 'plugins' );
|
||||
self::normalize_core_data( $status, $report_data );
|
||||
|
||||
// merge themes from report with all installed plugins before mapping into the Status_Model
|
||||
$installed_themes = Sync_Functions::get_themes();
|
||||
$last_report_themes = isset( $report_data->themes ) ? $report_data->themes : new \stdClass();
|
||||
$status->themes = self::merge_installed_and_checked_lists( $installed_themes, $last_report_themes, array( 'type' => 'themes' ) );
|
||||
|
||||
// normalize WordPress core report data and map into Status_Model
|
||||
$status->core = self::normalize_core_information( isset( $report_data->core ) ? $report_data->core : new \stdClass() );
|
||||
|
||||
// check if any installed items (themes, plugins, or core) have not been checked in the report
|
||||
$all_items = array_merge( $status->plugins, $status->themes, array( $status->core ) );
|
||||
$unchecked_items = array_filter(
|
||||
$all_items,
|
||||
function ( $item ) {
|
||||
return ! isset( $item->checked ) || ! $item->checked;
|
||||
}
|
||||
);
|
||||
$status->has_unchecked_items = ! empty( $unchecked_items );
|
||||
// sort extensions by number of threats
|
||||
$status->themes = self::sort_threats( $status->themes );
|
||||
$status->plugins = self::sort_threats( $status->plugins );
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
|
||||
* Normalize theme and plugin information from the Protect Report data source.
|
||||
*
|
||||
* @param array $installed The list of installed extensions, where each attribute key is the extension slug.
|
||||
* @param object $checked The list of checked extensions.
|
||||
* @param array $append Additional data to append to each result in the list.
|
||||
* @return array Normalized list of extensions.
|
||||
* @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @param object $status The status object to normalize.
|
||||
* @param object $report_data Data from the Protect Report.
|
||||
* @param string $extension_type The type of extension to normalize. Either 'themes' or 'plugins'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
|
||||
$new_list = array();
|
||||
foreach ( array_keys( $installed ) as $slug ) {
|
||||
protected static function normalize_extension_data( &$status, $report_data, $extension_type ) {
|
||||
if ( ! in_array( $extension_type, array( 'plugins', 'themes' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$checked = (object) $checked;
|
||||
$installed_extensions = 'plugins' === $extension_type ? Plugins_Installer::get_plugins() : Sync_Functions::get_themes();
|
||||
$checked_extensions = isset( $report_data->{ $extension_type } ) ? $report_data->{ $extension_type } : new \stdClass();
|
||||
|
||||
/**
|
||||
* Extension slug <=> threats data map.
|
||||
*
|
||||
* @var Extension_Model[] $extension_threats Array of Extension_Model objects indexed by slug.
|
||||
*/
|
||||
$extension_threats = array();
|
||||
|
||||
// Initialize the extension threats map with all extensions currently installed on the site
|
||||
foreach ( $installed_extensions as $slug => $installed_extension ) {
|
||||
$extension_threats[ $slug ] = new Extension_Model(
|
||||
array(
|
||||
'slug' => $slug,
|
||||
'name' => $installed_extension['Name'],
|
||||
'version' => $installed_extension['Version'],
|
||||
'type' => $extension_type,
|
||||
'checked' => isset( $checked_extensions->{ $slug } ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
foreach ( $checked_extensions as $slug => $checked_extension ) {
|
||||
$installed_extension = $installed_extensions[ $slug ] ?? null;
|
||||
|
||||
// extension is no longer installed on the site
|
||||
if ( ! $installed_extension ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extension = new Extension_Model(
|
||||
array_merge(
|
||||
array(
|
||||
'name' => $installed[ $slug ]['Name'],
|
||||
'version' => $installed[ $slug ]['Version'],
|
||||
'slug' => $slug,
|
||||
'threats' => array(),
|
||||
'checked' => false,
|
||||
),
|
||||
$append
|
||||
array(
|
||||
'name' => $installed_extension['Name'],
|
||||
'version' => $installed_extension['Version'],
|
||||
'slug' => $slug,
|
||||
'checked' => false,
|
||||
'type' => $extension_type,
|
||||
)
|
||||
);
|
||||
|
||||
if ( isset( $checked->{ $slug } ) && $checked->{ $slug }->version === $installed[ $slug ]['Version'] ) {
|
||||
$extension->version = $checked->{ $slug }->version;
|
||||
$extension->checked = true;
|
||||
|
||||
if ( is_array( $checked->{ $slug }->vulnerabilities ) ) {
|
||||
foreach ( $checked->{ $slug }->vulnerabilities as $threat ) {
|
||||
$extension->threats[] = new Threat_Model(
|
||||
array(
|
||||
'id' => $threat->id,
|
||||
'title' => $threat->title,
|
||||
'fixed_in' => $threat->fixed_in,
|
||||
'description' => isset( $threat->description ) ? $threat->description : null,
|
||||
'source' => isset( $threat->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $threat->id ) ) : null,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
// extension version has changed since the report
|
||||
if ( $installed_extension['Version'] !== $checked_extension->version ) {
|
||||
// maintain $status->{ themes|plugins } for backwards compatibility.
|
||||
$extension_threats[ $slug ] = $extension;
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_list[] = $extension;
|
||||
$extension->checked = true;
|
||||
$extension_threats[ $slug ] = $extension;
|
||||
|
||||
if ( is_array( $checked_extension->vulnerabilities ) && ! empty( $checked_extension->vulnerabilities ) ) {
|
||||
// normalize the vulnerabilities data
|
||||
$vulnerabilities = array_map(
|
||||
function ( $vulnerability ) {
|
||||
return new Vulnerability_Model( $vulnerability );
|
||||
},
|
||||
$checked_extension->vulnerabilities
|
||||
);
|
||||
|
||||
// convert the detected vulnerabilities into a vulnerable extension threat
|
||||
$threat = Threat_Model::generate_from_extension_vulnerabilities( $extension, $vulnerabilities );
|
||||
|
||||
$threat_extension = clone $extension;
|
||||
$extension_threat = clone $threat;
|
||||
|
||||
$extension_threat->extension = null;
|
||||
$extension_threats[ $slug ]->threats[] = $extension_threat;
|
||||
|
||||
$threat->extension = $threat_extension;
|
||||
$status->threats[] = $threat;
|
||||
}
|
||||
}
|
||||
|
||||
$new_list = parent::sort_threats( $new_list );
|
||||
|
||||
return $new_list;
|
||||
$status->{ $extension_type } = array_values( $extension_threats );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the WordPress version that was checked matches the current installed version.
|
||||
* Normalize the core information from the Protect Report data source.
|
||||
*
|
||||
* @param object $core_check The object returned by Protect wpcom endpoint.
|
||||
* @return object The object representing the current status of core checks.
|
||||
* @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @param object $status The status object to normalize.
|
||||
* @param object $report_data Data from the Protect Report.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function normalize_core_information( $core_check ) {
|
||||
protected static function normalize_core_data( &$status, $report_data ) {
|
||||
global $wp_version;
|
||||
|
||||
// Ensure the report data has the core property.
|
||||
if ( ! isset( $report_data->core ) || ! $report_data->core
|
||||
|| ! isset( $report_data->core->version ) || ! $report_data->core->version ) {
|
||||
$report_data->core = new \stdClass();
|
||||
$report_data->core->version = new \stdClass();
|
||||
}
|
||||
|
||||
$core = new Extension_Model(
|
||||
array(
|
||||
'type' => 'core',
|
||||
'name' => 'WordPress',
|
||||
'slug' => 'wordpress',
|
||||
'version' => $wp_version,
|
||||
'checked' => false,
|
||||
)
|
||||
);
|
||||
|
||||
if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
|
||||
if ( is_array( $core_check->vulnerabilities ) ) {
|
||||
$core->checked = true;
|
||||
$core->set_threats(
|
||||
array_map(
|
||||
function ( $vulnerability ) {
|
||||
$vulnerability->source = isset( $vulnerability->id ) ? Redirect::get_url( 'jetpack-protect-vul-info', array( 'path' => $vulnerability->id ) ) : null;
|
||||
return $vulnerability;
|
||||
},
|
||||
$core_check->vulnerabilities
|
||||
)
|
||||
);
|
||||
}
|
||||
// Core version has changed since the report.
|
||||
if ( $report_data->core->version !== $wp_version ) {
|
||||
// Maintain $status->core for backwards compatibility.
|
||||
$status->core = $core;
|
||||
return;
|
||||
}
|
||||
|
||||
return $core;
|
||||
// If we've made it this far, the core version has been checked.
|
||||
$core->checked = true;
|
||||
|
||||
// Generate a threat from core vulnerabilities.
|
||||
if ( is_array( $report_data->core->vulnerabilities ) && ! empty( $report_data->core->vulnerabilities ) ) {
|
||||
// normalize the vulnerabilities data
|
||||
$vulnerabilities = array_map(
|
||||
function ( $vulnerability ) {
|
||||
return new Vulnerability_Model( $vulnerability );
|
||||
},
|
||||
$report_data->core->vulnerabilities
|
||||
);
|
||||
|
||||
// convert the detected vulnerabilities into a vulnerable extension threat
|
||||
$threat = Threat_Model::generate_from_extension_vulnerabilities( $core, $vulnerabilities );
|
||||
|
||||
$threat_extension = clone $core;
|
||||
$extension_threat = clone $threat;
|
||||
|
||||
$core->threats[] = $extension_threat;
|
||||
$threat->extension = $threat_extension;
|
||||
|
||||
$status->threats[] = $threat;
|
||||
}
|
||||
|
||||
$status->core = $core;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort By Threats
|
||||
*
|
||||
* @param array<Extension_Model> $threats Array of threats to sort.
|
||||
*
|
||||
* @return array<Extension_Model> The sorted $threats array.
|
||||
*/
|
||||
protected static function sort_threats( $threats ) {
|
||||
usort(
|
||||
$threats,
|
||||
function ( $a, $b ) {
|
||||
return count( $a->threats ) - count( $b->threats );
|
||||
}
|
||||
);
|
||||
|
||||
return $threats;
|
||||
}
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ class REST_Controller {
|
||||
public static function api_check_plan() {
|
||||
$has_required_plan = Plan::has_required_plan();
|
||||
|
||||
return rest_ensure_response( $has_required_plan, 200 );
|
||||
return rest_ensure_response( $has_required_plan );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,7 +92,7 @@ class REST_Controller {
|
||||
*/
|
||||
public static function api_get_status( $request ) {
|
||||
$status = Status::get_status( $request['hard_refresh'] );
|
||||
return rest_ensure_response( $status, 200 );
|
||||
return rest_ensure_response( $status );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,8 @@
|
||||
/**
|
||||
* Class to handle the Scan Status of Jetpack Protect
|
||||
*
|
||||
* @phan-suppress PhanDeprecatedFunction -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @package automattic/jetpack-protect-status
|
||||
*/
|
||||
|
||||
@ -115,7 +117,10 @@ class Scan_Status extends Status {
|
||||
$response = Client::wpcom_json_api_request_as_blog(
|
||||
self::get_api_url(),
|
||||
'2',
|
||||
array( 'method' => 'GET' ),
|
||||
array(
|
||||
'method' => 'GET',
|
||||
'timeout' => 30,
|
||||
),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
@ -133,8 +138,11 @@ class Scan_Status extends Status {
|
||||
|
||||
/**
|
||||
* Normalize API Data
|
||||
*
|
||||
* Formats the payload from the Scan API into an instance of Status_Model.
|
||||
*
|
||||
* @phan-suppress PhanDeprecatedProperty -- Maintaining backwards compatibility.
|
||||
*
|
||||
* @param object $scan_data The data returned by the scan API.
|
||||
*
|
||||
* @return Status_Model
|
||||
@ -142,15 +150,35 @@ class Scan_Status extends Status {
|
||||
private static function normalize_api_data( $scan_data ) {
|
||||
global $wp_version;
|
||||
|
||||
$status = new Status_Model();
|
||||
$status->data_source = 'scan_api';
|
||||
$status->status = isset( $scan_data->state ) ? $scan_data->state : null;
|
||||
$status->num_threats = 0;
|
||||
$status->num_themes_threats = 0;
|
||||
$status->num_plugins_threats = 0;
|
||||
$status->has_unchecked_items = false;
|
||||
$status->current_progress = isset( $scan_data->current->progress ) ? $scan_data->current->progress : null;
|
||||
$installed_plugins = Plugins_Installer::get_plugins();
|
||||
$installed_themes = Sync_Functions::get_themes();
|
||||
|
||||
$plugins = array();
|
||||
$themes = array();
|
||||
$core = new Extension_Model(
|
||||
array(
|
||||
'name' => 'WordPress',
|
||||
'slug' => 'wordpress',
|
||||
'version' => $wp_version,
|
||||
'type' => 'core',
|
||||
'checked' => true, // to do: default to false once Scan API has manifest
|
||||
)
|
||||
);
|
||||
$files = array();
|
||||
|
||||
$status = new Status_Model(
|
||||
array(
|
||||
'data_source' => 'scan_api',
|
||||
'status' => isset( $scan_data->state ) ? $scan_data->state : null,
|
||||
'num_threats' => 0,
|
||||
'num_themes_threats' => 0,
|
||||
'num_plugins_threats' => 0,
|
||||
'has_unchecked_items' => false,
|
||||
'current_progress' => isset( $scan_data->current->progress ) ? $scan_data->current->progress : null,
|
||||
)
|
||||
);
|
||||
|
||||
// Format the "last checked" timestamp.
|
||||
if ( ! empty( $scan_data->most_recent->timestamp ) ) {
|
||||
$date = new \DateTime( $scan_data->most_recent->timestamp );
|
||||
if ( $date ) {
|
||||
@ -158,205 +186,158 @@ class Scan_Status extends Status {
|
||||
}
|
||||
}
|
||||
|
||||
$status->core = new Extension_Model(
|
||||
array(
|
||||
'type' => 'core',
|
||||
'name' => 'WordPress',
|
||||
'version' => $wp_version,
|
||||
'checked' => true, // to do: default to false once Scan API has manifest
|
||||
)
|
||||
);
|
||||
// Ensure all installed plugins and themes are represented in the status.
|
||||
foreach ( $installed_plugins as $path => $installed_plugin ) {
|
||||
$slug = str_replace( '.php', '', explode( '/', $path )[0] );
|
||||
$plugin = new Extension_Model(
|
||||
array(
|
||||
'name' => $installed_plugin['Name'],
|
||||
'version' => $installed_plugin['Version'],
|
||||
'slug' => $slug,
|
||||
'type' => 'plugins',
|
||||
'checked' => true, // to do: default to false once Scan API has manifest
|
||||
)
|
||||
);
|
||||
|
||||
$plugins[ $slug ] = $plugin;
|
||||
}
|
||||
foreach ( $installed_themes as $path => $installed_theme ) {
|
||||
$slug = str_replace( '.php', '', explode( '/', $path )[0] );
|
||||
$theme = new Extension_Model(
|
||||
array(
|
||||
'name' => $installed_theme['Name'],
|
||||
'version' => $installed_theme['Version'],
|
||||
'slug' => $slug,
|
||||
'type' => 'themes',
|
||||
'checked' => true, // to do: default to false once Scan API has manifest
|
||||
)
|
||||
);
|
||||
|
||||
$themes[ $slug ] = $theme;
|
||||
}
|
||||
|
||||
// Merge the threats into the status model.
|
||||
if ( isset( $scan_data->threats ) && is_array( $scan_data->threats ) ) {
|
||||
foreach ( $scan_data->threats as $threat ) {
|
||||
if ( isset( $threat->extension->type ) ) {
|
||||
if ( 'plugin' === $threat->extension->type ) {
|
||||
// add the extension if it does not yet exist in the status
|
||||
if ( ! isset( $status->plugins[ $threat->extension->slug ] ) ) {
|
||||
$status->plugins[ $threat->extension->slug ] = new Extension_Model(
|
||||
array(
|
||||
'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
|
||||
'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
|
||||
'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
|
||||
'type' => 'plugin',
|
||||
'checked' => true,
|
||||
'threats' => array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$status->plugins[ $threat->extension->slug ]->threats[] = new Threat_Model(
|
||||
array(
|
||||
'id' => isset( $threat->id ) ? $threat->id : null,
|
||||
'signature' => isset( $threat->signature ) ? $threat->signature : null,
|
||||
'title' => isset( $threat->title ) ? $threat->title : null,
|
||||
'description' => isset( $threat->description ) ? $threat->description : null,
|
||||
'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
|
||||
'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
|
||||
'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
|
||||
'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
|
||||
'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
|
||||
'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
|
||||
'severity' => isset( $threat->severity ) ? $threat->severity : null,
|
||||
'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
|
||||
'status' => isset( $threat->status ) ? $threat->status : null,
|
||||
'filename' => isset( $threat->filename ) ? $threat->filename : null,
|
||||
'context' => isset( $threat->context ) ? $threat->context : null,
|
||||
'source' => isset( $threat->source ) ? $threat->source : null,
|
||||
)
|
||||
);
|
||||
++$status->num_threats;
|
||||
++$status->num_plugins_threats;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'theme' === $threat->extension->type ) {
|
||||
// add the extension if it does not yet exist in the status
|
||||
if ( ! isset( $status->themes[ $threat->extension->slug ] ) ) {
|
||||
$status->themes[ $threat->extension->slug ] = new Extension_Model(
|
||||
array(
|
||||
'name' => isset( $threat->extension->name ) ? $threat->extension->name : null,
|
||||
'slug' => isset( $threat->extension->slug ) ? $threat->extension->slug : null,
|
||||
'version' => isset( $threat->extension->version ) ? $threat->extension->version : null,
|
||||
'type' => 'theme',
|
||||
'checked' => true,
|
||||
'threats' => array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$status->themes[ $threat->extension->slug ]->threats[] = new Threat_Model(
|
||||
array(
|
||||
'id' => isset( $threat->id ) ? $threat->id : null,
|
||||
'signature' => isset( $threat->signature ) ? $threat->signature : null,
|
||||
'title' => isset( $threat->title ) ? $threat->title : null,
|
||||
'description' => isset( $threat->description ) ? $threat->description : null,
|
||||
'vulnerability_description' => isset( $threat->vulnerability_description ) ? $threat->vulnerability_description : null,
|
||||
'fix_description' => isset( $threat->fix_description ) ? $threat->fix_description : null,
|
||||
'payload_subtitle' => isset( $threat->payload_subtitle ) ? $threat->payload_subtitle : null,
|
||||
'payload_description' => isset( $threat->payload_description ) ? $threat->payload_description : null,
|
||||
'first_detected' => isset( $threat->first_detected ) ? $threat->first_detected : null,
|
||||
'fixed_in' => isset( $threat->fixer->fixer ) && 'update' === $threat->fixer->fixer ? $threat->fixer->target : null,
|
||||
'severity' => isset( $threat->severity ) ? $threat->severity : null,
|
||||
'fixable' => isset( $threat->fixer ) ? $threat->fixer : null,
|
||||
'status' => isset( $threat->status ) ? $threat->status : null,
|
||||
'filename' => isset( $threat->filename ) ? $threat->filename : null,
|
||||
'context' => isset( $threat->context ) ? $threat->context : null,
|
||||
'source' => isset( $threat->source ) ? $threat->source : null,
|
||||
)
|
||||
);
|
||||
++$status->num_threats;
|
||||
++$status->num_themes_threats;
|
||||
continue;
|
||||
}
|
||||
foreach ( $scan_data->threats as $scan_threat ) {
|
||||
if ( isset( $scan_threat->fixable ) && $scan_threat->fixable ) {
|
||||
$status->fixable_threat_ids[] = $scan_threat->id;
|
||||
}
|
||||
|
||||
if ( isset( $threat->signature ) && 'Vulnerable.WP.Core' === $threat->signature ) {
|
||||
if ( $threat->version !== $wp_version ) {
|
||||
$threat = new Threat_Model(
|
||||
array(
|
||||
'id' => isset( $scan_threat->id ) ? $scan_threat->id : null,
|
||||
'signature' => isset( $scan_threat->signature ) ? $scan_threat->signature : null,
|
||||
'title' => isset( $scan_threat->title ) ? $scan_threat->title : null,
|
||||
'description' => isset( $scan_threat->description ) ? $scan_threat->description : null,
|
||||
'vulnerability_description' => isset( $scan_threat->vulnerability_description ) ? $scan_threat->vulnerability_description : null,
|
||||
'fix_description' => isset( $scan_threat->fix_description ) ? $scan_threat->fix_description : null,
|
||||
'payload_subtitle' => isset( $scan_threat->payload_subtitle ) ? $scan_threat->payload_subtitle : null,
|
||||
'payload_description' => isset( $scan_threat->payload_description ) ? $scan_threat->payload_description : null,
|
||||
'first_detected' => isset( $scan_threat->first_detected ) ? $scan_threat->first_detected : null,
|
||||
'fixed_in' => isset( $scan_threat->fixer->fixer ) && 'update' === $scan_threat->fixer->fixer ? $scan_threat->fixer->target : null,
|
||||
'severity' => isset( $scan_threat->severity ) ? $scan_threat->severity : null,
|
||||
'fixable' => isset( $scan_threat->fixer ) ? $scan_threat->fixer : null,
|
||||
'status' => isset( $scan_threat->status ) ? $scan_threat->status : null,
|
||||
'filename' => isset( $scan_threat->filename ) ? $scan_threat->filename : null,
|
||||
'context' => isset( $scan_threat->context ) ? $scan_threat->context : null,
|
||||
'source' => isset( $scan_threat->source ) ? $scan_threat->source : null,
|
||||
)
|
||||
);
|
||||
|
||||
// Theme and Plugin Threats
|
||||
if ( ! empty( $scan_threat->extension ) && in_array( $scan_threat->extension->type, array( 'plugin', 'theme' ), true ) ) {
|
||||
$installed_extension = 'plugin' === $scan_threat->extension->type ? ( $plugins[ $scan_threat->extension->slug ] ?? null ) : ( $themes[ $scan_threat->extension->slug ] ?? null );
|
||||
|
||||
// If the extension is no longer installed, skip this threat.
|
||||
// todo: use version_compare()
|
||||
if ( ! $installed_extension ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$status->core->threats[] = new Threat_Model(
|
||||
// Push the threat to the appropriate extension.
|
||||
switch ( $scan_threat->extension->type ) {
|
||||
case 'plugin':
|
||||
$plugins[ $scan_threat->extension->slug ]->threats[] = clone $threat;
|
||||
++$status->num_plugins_threats;
|
||||
break;
|
||||
case 'theme':
|
||||
$themes[ $scan_threat->extension->slug ]->threats[] = clone $threat;
|
||||
++$status->num_themes_threats;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$threat->extension = new Extension_Model(
|
||||
array(
|
||||
'id' => $threat->id,
|
||||
'signature' => $threat->signature,
|
||||
'title' => $threat->title,
|
||||
'description' => $threat->description,
|
||||
'first_detected' => $threat->first_detected,
|
||||
'severity' => $threat->severity,
|
||||
'name' => isset( $scan_threat->extension->name ) ? $scan_threat->extension->name : null,
|
||||
'slug' => isset( $scan_threat->extension->slug ) ? $scan_threat->extension->slug : null,
|
||||
'version' => isset( $scan_threat->extension->version ) ? $scan_threat->extension->version : null,
|
||||
'type' => $scan_threat->extension->type . 's',
|
||||
'checked' => $installed_extension->version === $scan_threat->extension->version,
|
||||
)
|
||||
);
|
||||
++$status->num_threats;
|
||||
} elseif ( isset( $threat->signature ) && 'Vulnerable.WP.Core' === $threat->signature ) {
|
||||
// Vulnerable WordPress Core Version Threats
|
||||
|
||||
continue;
|
||||
// If the core version has changed, skip this threat.
|
||||
// todo: use version_compare()
|
||||
if ( $scan_threat->version !== $wp_version ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$core->threats[] = $threat;
|
||||
} elseif ( ! empty( $threat->filename ) ) {
|
||||
// File Threats
|
||||
$files[] = $threat;
|
||||
}
|
||||
|
||||
if ( ! empty( $threat->filename ) ) {
|
||||
$status->files[] = new Threat_Model( $threat );
|
||||
++$status->num_threats;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! empty( $threat->table ) ) {
|
||||
$status->database[] = new Threat_Model( $threat );
|
||||
++$status->num_threats;
|
||||
continue;
|
||||
}
|
||||
$status->threats[] = $threat;
|
||||
++$status->num_threats;
|
||||
}
|
||||
}
|
||||
|
||||
$installed_plugins = Plugins_Installer::get_plugins();
|
||||
$status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $status->plugins, array( 'type' => 'plugins' ), true );
|
||||
$status->threats = static::sort_threats( $status->threats );
|
||||
|
||||
$installed_themes = Sync_Functions::get_themes();
|
||||
$status->themes = self::merge_installed_and_checked_lists( $installed_themes, $status->themes, array( 'type' => 'themes' ), true );
|
||||
|
||||
foreach ( array_merge( $status->themes, $status->plugins ) as $extension ) {
|
||||
if ( ! $extension->checked ) {
|
||||
$status->has_unchecked_items = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// maintain deprecated properties for backwards compatibility
|
||||
$status->plugins = array_values( $plugins );
|
||||
$status->themes = array_values( $themes );
|
||||
$status->core = $core;
|
||||
$status->files = $files;
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the list of installed extensions with the list of extensions that were checked for known vulnerabilities and return a normalized list to be used in the UI
|
||||
* Sort By Threats
|
||||
*
|
||||
* @param array $installed The list of installed extensions, where each attribute key is the extension slug.
|
||||
* @param object $checked The list of checked extensions.
|
||||
* @param array $append Additional data to append to each result in the list.
|
||||
* @return array Normalized list of extensions.
|
||||
* @param array<Threat_Model> $threats Array of threats to sort.
|
||||
*
|
||||
* @return array<Threat_Model> The sorted $threats array.
|
||||
*/
|
||||
protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
|
||||
$new_list = array();
|
||||
$checked = (object) $checked;
|
||||
|
||||
foreach ( array_keys( $installed ) as $slug ) {
|
||||
/**
|
||||
* Extension Type Map
|
||||
*
|
||||
* @var array $extension_type_map Key value pairs of extension types and their corresponding
|
||||
* identifier used by the Scan API data source.
|
||||
*/
|
||||
$extension_type_map = array(
|
||||
'themes' => 'r1',
|
||||
'plugins' => 'r2',
|
||||
);
|
||||
|
||||
$version = $installed[ $slug ]['Version'];
|
||||
$short_slug = str_replace( '.php', '', explode( '/', $slug )[0] );
|
||||
$scanifest_slug = $extension_type_map[ $append['type'] ] . ":$short_slug@$version";
|
||||
|
||||
$extension = new Extension_Model(
|
||||
array_merge(
|
||||
array(
|
||||
'name' => $installed[ $slug ]['Name'],
|
||||
'version' => $version,
|
||||
'slug' => $slug,
|
||||
'threats' => array(),
|
||||
'checked' => false,
|
||||
),
|
||||
$append
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! isset( $checked->extensions ) // no extension data available from Scan API
|
||||
|| is_array( $checked->extensions ) && in_array( $scanifest_slug, $checked->extensions, true ) // extension data matches Scan API
|
||||
) {
|
||||
$extension->checked = true;
|
||||
if ( isset( $checked->{ $short_slug }->threats ) ) {
|
||||
$extension->threats = $checked->{ $short_slug }->threats;
|
||||
protected static function sort_threats( $threats ) {
|
||||
usort(
|
||||
$threats,
|
||||
function ( $a, $b ) {
|
||||
// Order by active status first...
|
||||
if ( $a->status !== $b->status ) {
|
||||
return 'active' === $a->status ? -1 : 1;
|
||||
}
|
||||
|
||||
// ...then by severity...
|
||||
if ( $a->severity !== $b->severity ) {
|
||||
return $a->severity > $b->severity ? -1 : 1;
|
||||
}
|
||||
|
||||
// ...then date added.
|
||||
if ( $a->first_detected !== $b->first_detected ) {
|
||||
return strtotime( $a->first_detected ) < strtotime( $b->first_detected ) ? -1 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
);
|
||||
|
||||
$new_list[] = $extension;
|
||||
|
||||
}
|
||||
|
||||
$new_list = parent::sort_threats( $new_list );
|
||||
|
||||
return $new_list;
|
||||
return $threats;
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
|
||||
namespace Automattic\Jetpack\Protect_Status;
|
||||
|
||||
use Automattic\Jetpack\Protect_Models\Extension_Model;
|
||||
use Automattic\Jetpack\Protect_Models\Status_Model;
|
||||
|
||||
/**
|
||||
@ -15,7 +14,7 @@ use Automattic\Jetpack\Protect_Models\Status_Model;
|
||||
*/
|
||||
class Status {
|
||||
|
||||
const PACKAGE_VERSION = '0.1.5';
|
||||
const PACKAGE_VERSION = '0.5.8';
|
||||
/**
|
||||
* Name of the option where status is stored
|
||||
*
|
||||
@ -58,7 +57,7 @@ class Status {
|
||||
* @return Status_Model
|
||||
*/
|
||||
public static function get_status( $refresh_from_wpcom = false ) {
|
||||
$use_scan_status = Plan::has_required_plan();
|
||||
$use_scan_status = Plan::has_required_plan( $refresh_from_wpcom );
|
||||
|
||||
if ( defined( 'JETPACK_PROTECT_DEV__DATA_SOURCE' ) ) {
|
||||
if ( 'scan_api' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
|
||||
@ -163,7 +162,7 @@ class Status {
|
||||
*/
|
||||
public static function get_total_threats() {
|
||||
$status = static::get_status();
|
||||
return isset( $status->num_threats ) && is_int( $status->num_threats ) ? $status->num_threats : 0;
|
||||
return count( $status->threats );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,18 +171,15 @@ class Status {
|
||||
* @return array
|
||||
*/
|
||||
public static function get_all_threats() {
|
||||
return array_merge(
|
||||
self::get_wordpress_threats(),
|
||||
self::get_themes_threats(),
|
||||
self::get_plugins_threats(),
|
||||
self::get_files_threats(),
|
||||
self::get_database_threats()
|
||||
);
|
||||
$status = static::get_status();
|
||||
return $status->threats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get threats found for WordPress core
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_wordpress_threats() {
|
||||
@ -193,6 +189,8 @@ class Status {
|
||||
/**
|
||||
* Get threats found for themes
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_themes_threats() {
|
||||
@ -202,6 +200,8 @@ class Status {
|
||||
/**
|
||||
* Get threats found for plugins
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_plugins_threats() {
|
||||
@ -211,6 +211,8 @@ class Status {
|
||||
/**
|
||||
* Get threats found for files
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_files_threats() {
|
||||
@ -220,6 +222,8 @@ class Status {
|
||||
/**
|
||||
* Get threats found for plugins
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_database_threats() {
|
||||
@ -236,56 +240,41 @@ class Status {
|
||||
public static function get_threats( $type ) {
|
||||
$status = static::get_status();
|
||||
|
||||
if ( 'core' === $type ) {
|
||||
return isset( $status->$type ) && ! empty( $status->$type->threats ) ? $status->$type->threats : array();
|
||||
}
|
||||
|
||||
if ( 'files' === $type || 'database' === $type ) {
|
||||
return isset( $status->$type ) && ! empty( $status->$type ) ? $status->$type : array();
|
||||
}
|
||||
|
||||
$threats = array();
|
||||
if ( isset( $status->$type ) ) {
|
||||
foreach ( (array) $status->$type as $item ) {
|
||||
if ( ! empty( $item->threats ) ) {
|
||||
$threats = array_merge( $threats, $item->threats );
|
||||
if ( in_array( $type, array( 'plugin', 'theme', 'core' ), true ) ) {
|
||||
return array_filter(
|
||||
$status->threats,
|
||||
function ( $threat ) use ( $type ) {
|
||||
return isset( $threat->extension ) && $type === $threat->extension->type;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $threats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the WordPress version that was checked matches the current installed version.
|
||||
*
|
||||
* @param object $core_check The object returned by Protect wpcom endpoint.
|
||||
* @return object The object representing the current status of core checks.
|
||||
*/
|
||||
protected static function normalize_core_information( $core_check ) {
|
||||
global $wp_version;
|
||||
|
||||
$core = new Extension_Model(
|
||||
array(
|
||||
'type' => 'core',
|
||||
'name' => 'WordPress',
|
||||
'version' => $wp_version,
|
||||
'checked' => false,
|
||||
)
|
||||
);
|
||||
|
||||
if ( isset( $core_check->version ) && $core_check->version === $wp_version ) {
|
||||
if ( is_array( $core_check->vulnerabilities ) ) {
|
||||
$core->checked = true;
|
||||
$core->set_threats( $core_check->vulnerabilities );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $core;
|
||||
if ( 'files' === $type ) {
|
||||
return array_filter(
|
||||
$status->threats,
|
||||
function ( $threat ) {
|
||||
return ! empty( $threat->filename );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'database' === $type ) {
|
||||
return array_filter(
|
||||
$status->threats,
|
||||
function ( $threat ) {
|
||||
return ! empty( $threat->table );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return $status->threats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort By Threats
|
||||
*
|
||||
* @deprecated 0.3.0
|
||||
*
|
||||
* @param array<object> $threats Array of threats to sort.
|
||||
*
|
||||
* @return array<object> The sorted $threats array.
|
||||
|
Reference in New Issue
Block a user