updated plugin Jetpack Protect version 4.0.0

This commit is contained in:
2025-04-29 21:19:56 +00:00
committed by Gitium
parent eb9181b250
commit ebd40ef928
265 changed files with 11864 additions and 3987 deletions

View File

@ -5,6 +5,82 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.8] - 2025-03-21
### Changed
- Internal updates.
## [0.5.7] - 2025-03-18
### Changed
- Update dependencies.
## [0.5.6] - 2025-03-17
### Changed
- Internal updates.
## [0.5.5] - 2025-03-12
### Changed
- Internal updates.
## [0.5.4] - 2025-03-05
### Changed
- Internal updates.
## [0.5.3] - 2025-03-03
### Changed
- Internal updates.
## [0.5.2] - 2025-02-24
### Changed
- Update dependencies.
## [0.5.1] - 2025-02-11
### Fixed
- Protect Status: Ensure vulnerabilities property is always an array. [#41694]
## [0.5.0] - 2025-02-10
### Changed
- Combine multiple vulnerability results for the same extension into a single vulnerable extension threat result. [#40863]
## [0.4.3] - 2025-02-03
### Fixed
- Code: Remove extra params on function calls. [#41263]
- Fix a bug when core version data is not interpreted correctly from the report data response. [#41503]
## [0.4.2] - 2025-01-20
### Fixed
- Fix Current_Plan::supports() call from breaking cache on every call. [#41010]
## [0.4.1] - 2024-12-23
### Fixed
- Fix PHP warnings caused by uninstalled extensions. [#40622]
## [0.4.0] - 2024-12-04
### Added
- Add extension data to threats. [#40400]
## [0.3.1] - 2024-11-25
### Changed
- Updated dependencies. [#40286]
## [0.3.0] - 2024-11-14
### Added
- Added threats property to protect status. [#40097]
### Removed
- General: Update minimum PHP version to 7.2. [#40147]
## [0.2.2] - 2024-11-04
### Added
- Enable test coverage. [#39961]
## [0.2.1] - 2024-10-29
### Changed
- Internal updates.
## [0.2.0] - 2024-09-23
### Changed
- Adds a fixable_threats status property [#39125]
## [0.1.5] - 2024-09-05
### Changed
- Update dependencies.
@ -32,6 +108,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Updated package dependencies. [#37894]
[0.5.8]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.7...v0.5.8
[0.5.7]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.6...v0.5.7
[0.5.6]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.5...v0.5.6
[0.5.5]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.4...v0.5.5
[0.5.4]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.3...v0.5.4
[0.5.3]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.2...v0.5.3
[0.5.2]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.1...v0.5.2
[0.5.1]: https://github.com/Automattic/jetpack-protect-status/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/Automattic/jetpack-protect-status/compare/v0.4.3...v0.5.0
[0.4.3]: https://github.com/Automattic/jetpack-protect-status/compare/v0.4.2...v0.4.3
[0.4.2]: https://github.com/Automattic/jetpack-protect-status/compare/v0.4.1...v0.4.2
[0.4.1]: https://github.com/Automattic/jetpack-protect-status/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/Automattic/jetpack-protect-status/compare/v0.3.1...v0.4.0
[0.3.1]: https://github.com/Automattic/jetpack-protect-status/compare/v0.3.0...v0.3.1
[0.3.0]: https://github.com/Automattic/jetpack-protect-status/compare/v0.2.2...v0.3.0
[0.2.2]: https://github.com/Automattic/jetpack-protect-status/compare/v0.2.1...v0.2.2
[0.2.1]: https://github.com/Automattic/jetpack-protect-status/compare/v0.2.0...v0.2.1
[0.2.0]: https://github.com/Automattic/jetpack-protect-status/compare/v0.1.5...v0.2.0
[0.1.5]: https://github.com/Automattic/jetpack-protect-status/compare/v0.1.4...v0.1.5
[0.1.4]: https://github.com/Automattic/jetpack-protect-status/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/Automattic/jetpack-protect-status/compare/v0.1.2...v0.1.3

View File

@ -4,17 +4,18 @@
"type": "jetpack-library",
"license": "GPL-2.0-or-later",
"require": {
"php": ">=7.0",
"automattic/jetpack-connection": "^4.0.0",
"automattic/jetpack-plugins-installer": "^0.4.3",
"automattic/jetpack-sync": "^3.10.0",
"automattic/jetpack-protect-models": "^0.2.1",
"automattic/jetpack-plans": "^0.4.10"
"php": ">=7.2",
"automattic/jetpack-connection": "^6.7.7",
"automattic/jetpack-plugins-installer": "^0.5.4",
"automattic/jetpack-sync": "^4.9.2",
"automattic/jetpack-protect-models": "^0.5.4",
"automattic/jetpack-plans": "^0.6.1"
},
"require-dev": {
"yoast/phpunit-polyfills": "^1.1.1",
"automattic/jetpack-changelogger": "^4.2.6",
"automattic/wordbless": "dev-master"
"yoast/phpunit-polyfills": "^3.0.0",
"automattic/jetpack-changelogger": "^6.0.2",
"automattic/jetpack-test-environment": "@dev",
"automattic/phpunit-select-config": "^1.0.1"
},
"autoload": {
"classmap": [
@ -25,10 +26,11 @@
"build-development": "echo 'Add your build step to composer.json, please!'",
"build-production": "echo 'Add your build step to composer.json, please!'",
"phpunit": [
"./vendor/phpunit/phpunit/phpunit --colors=always"
"phpunit-select-config phpunit.#.xml.dist --colors=always"
],
"test-coverage": [
"php -dpcov.directory=. ./vendor/bin/phpunit-select-config phpunit.#.xml.dist --coverage-php \"$COVERAGE_DIR/php.cov\""
],
"post-install-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"post-update-cmd": "WorDBless\\Composer\\InstallDropin::copy",
"test-php": [
"@composer phpunit"
]
@ -43,7 +45,7 @@
"extra": {
"autotagger": true,
"branch-alias": {
"dev-trunk": "0.1.x-dev"
"dev-trunk": "0.5.x-dev"
},
"changelogger": {
"link-template": "https://github.com/Automattic/jetpack-protect-status/compare/v${old}...v${new}"

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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 );
}
/**

View File

@ -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;
}
}

View File

@ -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.