updated plugin Jetpack Protect version 3.0.2

This commit is contained in:
2024-10-09 12:44:31 +00:00
committed by Gitium
parent a35dc419bc
commit f970470c59
283 changed files with 6970 additions and 2338 deletions

View File

@ -20,10 +20,13 @@ use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Protect\Onboarding;
use Automattic\Jetpack\Protect\Plan;
use Automattic\Jetpack\Protect\REST_Controller;
use Automattic\Jetpack\Protect\Scan_History;
use Automattic\Jetpack\Protect\Site_Health;
use Automattic\Jetpack\Protect\Status;
use Automattic\Jetpack\Protect_Status\Plan;
use Automattic\Jetpack\Protect_Status\Protect_Status;
use Automattic\Jetpack\Protect_Status\Scan_Status;
use Automattic\Jetpack\Protect_Status\Status;
use Automattic\Jetpack\Status as Jetpack_Status;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Automattic\Jetpack\Sync\Sender;
@ -153,7 +156,8 @@ class Jetpack_Protect {
$menu_label,
'manage_options',
'jetpack-protect',
array( $this, 'plugin_settings_page' )
array( $this, 'plugin_settings_page' ),
5
);
add_action( 'load-' . $page_suffix, array( $this, 'enqueue_admin_scripts' ) );
@ -211,6 +215,7 @@ class Jetpack_Protect {
'apiNonce' => wp_create_nonce( 'wp_rest' ),
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
'status' => Status::get_status( $refresh_status_from_wpcom ),
'scanHistory' => Scan_History::get_scan_history( $refresh_status_from_wpcom ),
'installedPlugins' => Plugins_Installer::get_plugins(),
'installedThemes' => Sync_Functions::get_themes(),
'wpVersion' => $wp_version,
@ -231,6 +236,7 @@ class Jetpack_Protect {
'isUpdating' => false,
'config' => Waf_Runner::get_config(),
'stats' => self::get_waf_stats(),
'globalStats' => Waf_Stats::get_global_stats(),
),
);
@ -293,7 +299,9 @@ class Jetpack_Protect {
$manager = new Connection_Manager( 'jetpack-protect' );
$manager->remove_connection();
Status::delete_option();
Protect_Status::delete_option();
Scan_Status::delete_option();
Scan_History::delete_option();
}
/**

View File

@ -1,111 +0,0 @@
<?php
/**
* Class to handle the Protect plan
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Current_Plan;
/**
* The Plan class.
*/
class Plan {
/**
* The meta name used to store the cache date
*
* @var string
*/
const CACHE_DATE_META_NAME = 'protect-cache-date';
/**
* Valid pediord for the cache: One week.
*/
const CACHE_VALIDITY_PERIOD = 7 * DAY_IN_SECONDS;
/**
* The meta name used to store the cache
*
* @var string
*/
const CACHE_META_NAME = 'protect-cache';
/**
* Checks if the cache is old, meaning we need to fetch new data from WPCOM
*/
private static function is_cache_old() {
if ( empty( self::get_product_from_cache() ) ) {
return true;
}
$cache_date = get_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, true );
return time() - (int) $cache_date > ( self::CACHE_VALIDITY_PERIOD );
}
/**
* Gets the product list from the user cache
*/
private static function get_product_from_cache() {
return get_user_meta( get_current_user_id(), self::CACHE_META_NAME, true );
}
/**
* Gets the product data
*
* @param string $wpcom_product The product slug.
* @return array
*/
public static function get_product( $wpcom_product = 'jetpack_scan' ) {
if ( ! self::is_cache_old() ) {
return self::get_product_from_cache();
}
$request_url = 'https://public-api.wordpress.com/rest/v1.1/products?locale=' . get_user_locale() . '&type=jetpack';
$wpcom_request = wp_remote_get( esc_url_raw( $request_url ) );
$response_code = wp_remote_retrieve_response_code( $wpcom_request );
if ( 200 === $response_code ) {
$products = json_decode( wp_remote_retrieve_body( $wpcom_request ) );
// Pick the desired product...
$product = $products->{$wpcom_product};
// ... and store it into the cache.
update_user_meta( get_current_user_id(), self::CACHE_DATE_META_NAME, time() );
update_user_meta( get_current_user_id(), self::CACHE_META_NAME, $product );
return $product;
}
return new \WP_Error(
'failed_to_fetch_data',
esc_html__( 'Unable to fetch the requested data.', 'jetpack-protect' ),
array(
'status' => $response_code,
'request' => $wpcom_request,
)
);
}
/**
* Has Required Plan
*
* @param bool $force_refresh Refresh the local plan cache from wpcom.
* @return bool True when the site has a plan or product that supports the paid Protect tier.
*/
public static function has_required_plan( $force_refresh = false ) {
static $has_scan = null;
if ( null === $has_scan || $force_refresh ) {
$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 );
$has_scan_product = count( array_intersect( array( 'jetpack_scan', 'jetpack_scan_monthly' ), $products ) ) > 0;
$has_scan = $plan_supports_scan || $has_scan_product;
}
return $has_scan;
}
}

View File

@ -1,258 +0,0 @@
<?php
/**
* Class to handle the Protect Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Redirect;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching and caching the Status of vulnerabilities check from the WPCOM servers
*/
class Protect_Status extends Status {
/**
* WPCOM endpoint
*
* @var string
*/
const REST_API_BASE = '/sites/%d/jetpack-protect-status';
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_protect_status';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_protect_status_time';
/**
* Gets the current status of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status( $refresh_from_wpcom = false ) {
if ( self::$status !== null ) {
return self::$status;
}
if ( $refresh_from_wpcom || ! self::should_use_cache() || self::is_cache_expired() ) {
$status = self::fetch_from_server();
} else {
$status = self::get_from_options();
}
if ( is_wp_error( $status ) ) {
$status = new Status_Model(
array(
'error' => true,
'error_code' => $status->get_error_code(),
'error_message' => $status->get_error_message(),
)
);
} else {
$status = self::normalize_protect_report_data( $status );
}
self::$status = $status;
return $status;
}
/**
* Gets the WPCOM API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::REST_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the status from WPCOM servers
*
* @return WP_Error|array
*/
public static function fetch_from_server() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
self::get_api_url(),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Protect Status data from server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
self::update_status_option( $body );
return $body;
}
/**
* Normalize data from the Protect Report data source.
*
* @param object $report_data Data from the Protect Report.
* @return Status_Model
*/
protected static function normalize_protect_report_data( $report_data ) {
$status = new Status_Model();
$status->data_source = 'protect_report';
// map report data properties directly into the Status_Model
$status->status = isset( $report_data->status ) ? $report_data->status : null;
$status->last_checked = isset( $report_data->last_checked ) ? $report_data->last_checked : null;
$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;
// 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' ) );
// 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 );
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
*
* @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.
*/
protected static function merge_installed_and_checked_lists( $installed, $checked, $append ) {
$new_list = array();
foreach ( array_keys( $installed ) as $slug ) {
$checked = (object) $checked;
$extension = new Extension_Model(
array_merge(
array(
'name' => $installed[ $slug ]['Name'],
'version' => $installed[ $slug ]['Version'],
'slug' => $slug,
'threats' => array(),
'checked' => false,
),
$append
)
);
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,
)
);
}
}
}
$new_list[] = $extension;
}
$new_list = parent::sort_threats( $new_list );
return $new_list;
}
/**
* 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(
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
)
);
}
}
return $core;
}
}

View File

@ -10,6 +10,7 @@
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Rest_Authentication as Connection_Rest_Authentication;
use Automattic\Jetpack\Protect_Status\REST_Controller as Protect_Status_REST_Controller;
use Automattic\Jetpack\Waf\Waf_Runner;
use Jetpack_Protect;
use WP_Error;
@ -40,41 +41,7 @@ class REST_Controller {
* @return void
*/
public static function register_rest_endpoints() {
register_rest_route(
'jetpack-protect/v1',
'check-plan',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::api_check_plan',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'status',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::api_get_status',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'clear-scan-cache',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::api_clear_scan_cache',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
Protect_Status_REST_Controller::register_rest_endpoints();
register_rest_route(
'jetpack-protect/v1',
@ -88,6 +55,18 @@ class REST_Controller {
)
);
register_rest_route(
'jetpack-protect/v1',
'unignore-threat',
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => __CLASS__ . '::api_unignore_threat',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
register_rest_route(
'jetpack-protect/v1',
'fix-threats',
@ -231,44 +210,18 @@ class REST_Controller {
},
)
);
}
/**
* Return site plan data for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_check_plan() {
$has_required_plan = Plan::has_required_plan();
return rest_ensure_response( $has_required_plan, 200 );
}
/**
* Return Protect Status for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_get_status( $request ) {
$status = Status::get_status( $request['hard_refresh'] );
return rest_ensure_response( $status, 200 );
}
/**
* Clear the Scan_Status cache for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_clear_scan_cache() {
$cache_cleared = Scan_Status::delete_option();
if ( ! $cache_cleared ) {
return new WP_REST_Response( 'An error occured while attempting to clear the Jetpack Scan cache.', 500 );
}
return new WP_REST_Response( 'Jetpack Scan cache cleared.' );
register_rest_route(
'jetpack-protect/v1',
'scan-history',
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => __CLASS__ . '::api_get_scan_history',
'permission_callback' => function () {
return current_user_can( 'manage_options' );
},
)
);
}
/**
@ -292,6 +245,27 @@ class REST_Controller {
return new WP_REST_Response( 'Threat ignored.' );
}
/**
* Unignores a threat for the API endpoint
*
* @param WP_REST_Request $request The request object.
*
* @return WP_REST_Response
*/
public static function api_unignore_threat( $request ) {
if ( ! $request['threat_id'] ) {
return new WP_REST_Response( 'Missing threat ID.', 400 );
}
$threat_ignored = Threats::unignore_threat( $request['threat_id'] );
if ( ! $threat_ignored ) {
return new WP_REST_Response( 'An error occured while attempting to unignore the threat.', 500 );
}
return new WP_REST_Response( 'Threat unignored.' );
}
/**
* Fixes threats for the API endpoint
*
@ -480,4 +454,14 @@ class REST_Controller {
return new WP_REST_Response( 'Onboarding step(s) completed.' );
}
/**
* Return Scan History for the API endpoint
*
* @return WP_REST_Response
*/
public static function api_get_scan_history() {
$scan_history = Scan_History::get_scan_history( false );
return rest_ensure_response( $scan_history, 200 );
}
}

View File

@ -0,0 +1,351 @@
<?php
/**
* Class to handle the Scan Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Protect_Models\Extension_Model;
use Automattic\Jetpack\Protect_Models\History_Model;
use Automattic\Jetpack\Protect_Models\Threat_Model;
use Automattic\Jetpack\Protect_Status\Plan;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching of threats from the Scan API
*/
class Scan_History {
/**
* Scan endpoint
*
* @var string
*/
const SCAN_HISTORY_API_BASE = '/sites/%d/scan/history';
/**
* Name of the option where history is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_scan_history';
/**
* Name of the option where the timestamp of the history is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_scan_history_timestamp';
/**
* Time in seconds that the cache should last
*
* @var int
*/
const OPTION_EXPIRES_AFTER = 300; // 5 minutes.
/**
* Memoization for the current history
*
* @var null|History_Model
*/
public static $history = null;
/**
* Checks if the current cached history is expired and should be renewed
*
* @return boolean
*/
public static function is_cache_expired() {
$option_timestamp = get_option( static::OPTION_TIMESTAMP_NAME );
if ( ! $option_timestamp ) {
return true;
}
return time() > (int) $option_timestamp;
}
/**
* Checks if we should consider the stored cache or bypass it
*
* @return boolean
*/
public static function should_use_cache() {
return ! ( ( defined( 'JETPACK_PROTECT_DEV__BYPASS_CACHE' ) && JETPACK_PROTECT_DEV__BYPASS_CACHE ) );
}
/**
* Gets the current cached history
*
* @return bool|array False if value is not found. Array with values if cache is found.
*/
public static function get_from_options() {
return maybe_unserialize( get_option( static::OPTION_NAME ) );
}
/**
* Updated the cached history and its timestamp
*
* @param array $history The new history to be cached.
* @return void
*/
public static function update_history_option( $history ) {
// TODO: Sanitize $history.
update_option( static::OPTION_NAME, maybe_serialize( $history ) );
update_option( static::OPTION_TIMESTAMP_NAME, time() + static::OPTION_EXPIRES_AFTER );
}
/**
* Delete the cached history and its timestamp
*
* @return bool Whether all related history options were successfully deleted.
*/
public static function delete_option() {
$option_deleted = delete_option( static::OPTION_NAME );
$option_timestamp_deleted = delete_option( static::OPTION_TIMESTAMP_NAME );
return $option_deleted && $option_timestamp_deleted;
}
/**
* Gets the current history of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and history cache from wpcom.
* @return History_Model|bool
*/
public static function get_scan_history( $refresh_from_wpcom = false ) {
$has_required_plan = Plan::has_required_plan();
if ( ! $has_required_plan ) {
return false;
}
if ( self::$history !== null ) {
return self::$history;
}
if ( $refresh_from_wpcom || ! self::should_use_cache() || self::is_cache_expired() ) {
$history = self::fetch_from_api();
} else {
$history = self::get_from_options();
}
if ( is_wp_error( $history ) ) {
$history = new History_Model(
array(
'error' => true,
'error_code' => $history->get_error_code(),
'error_message' => $history->get_error_message(),
)
);
} else {
$history = self::normalize_api_data( $history );
}
self::$history = $history;
return $history;
}
/**
* Gets the Scan API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::SCAN_HISTORY_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the history data from the Scan API
*
* @return WP_Error|array
*/
public static function fetch_from_api() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
$api_url,
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Scan history from the server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
$body->last_checked = ( new \DateTime() )->format( 'Y-m-d H:i:s' );
self::update_history_option( $body );
return $body;
}
/**
* Normalize API Data
* Formats the payload from the Scan API into an instance of History_Model.
*
* @param object $scan_data The data returned by the scan API.
* @return History_Model
*/
private static function normalize_api_data( $scan_data ) {
$history = new History_Model();
$history->num_threats = 0;
$history->num_core_threats = 0;
$history->num_plugins_threats = 0;
$history->num_themes_threats = 0;
$history->last_checked = $scan_data->last_checked;
if ( empty( $scan_data->threats ) || ! is_array( $scan_data->threats ) ) {
return $history;
}
foreach ( $scan_data->threats as $threat ) {
if ( isset( $threat->extension->type ) ) {
if ( 'plugin' === $threat->extension->type ) {
self::handle_extension_threats( $threat, $history, 'plugin' );
continue;
}
if ( 'theme' === $threat->extension->type ) {
self::handle_extension_threats( $threat, $history, 'theme' );
continue;
}
}
if ( 'Vulnerable.WP.Core' === $threat->signature ) {
self::handle_core_threats( $threat, $history );
continue;
}
self::handle_additional_threats( $threat, $history );
}
return $history;
}
/**
* Handles threats for extensions such as plugins or themes.
*
* @param object $threat The threat object.
* @param object $history The history object.
* @param string $type The type of extension ('plugin' or 'theme').
* @return void
*/
private static function handle_extension_threats( $threat, $history, $type ) {
$extension_list = $type === 'plugin' ? 'plugins' : 'themes';
$extensions = &$history->{ $extension_list};
$found_index = null;
// Check if the extension does not exist in the array
foreach ( $extensions as $index => $extension ) {
if ( $extension->slug === $threat->extension->slug ) {
$found_index = $index;
break;
}
}
// Add the extension if it does not yet exist in the history
if ( $found_index === null ) {
$new_extension = new Extension_Model(
array(
'name' => $threat->extension->name ?? null,
'slug' => $threat->extension->slug ?? null,
'version' => $threat->extension->version ?? null,
'type' => $type,
'checked' => true,
'threats' => array(),
)
);
$extensions[] = $new_extension;
$found_index = array_key_last( $extensions );
}
// Add the threat to the found extension
$extensions[ $found_index ]->threats[] = new Threat_Model( $threat );
// Increment the threat counts
++$history->num_threats;
if ( $type === 'plugin' ) {
++$history->num_plugins_threats;
} elseif ( $type === 'theme' ) {
++$history->num_themes_threats;
}
}
/**
* Handles core threats
*
* @param object $threat The threat object.
* @param object $history The history object.
* @return void
*/
private static function handle_core_threats( $threat, $history ) {
// Check if the core version does not exist in the array
$found_index = null;
foreach ( $history->core as $index => $core ) {
if ( $core->version === $threat->version ) {
$found_index = $index;
break;
}
}
// Add the extension if it does not yet exist in the history
if ( null === $found_index ) {
$new_core = new Extension_Model(
array(
'name' => 'WordPress',
'version' => $threat->version,
'type' => 'core',
'checked' => true,
'threats' => array(),
)
);
$history->core[] = $new_core;
$found_index = array_key_last( $history->core );
}
// Add the threat to the found core
$history->core[ $found_index ]->threats[] = new Threat_Model( $threat );
++$history->num_threats;
++$history->num_core_threats;
}
/**
* Handles additional threats that are not core, plugin or theme
*
* @param object $threat The threat object.
* @param object $history The history object.
* @return void
*/
private static function handle_additional_threats( $threat, $history ) {
if ( ! empty( $threat->filename ) ) {
$history->files[] = new Threat_Model( $threat );
++$history->num_threats;
} elseif ( ! empty( $threat->table ) ) {
$history->database[] = new Threat_Model( $threat );
++$history->num_threats;
}
}
}

View File

@ -1,359 +0,0 @@
<?php
/**
* Class to handle the Scan Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Plugins_Installer;
use Automattic\Jetpack\Sync\Functions as Sync_Functions;
use Jetpack_Options;
use WP_Error;
/**
* Class that handles fetching of threats from the Scan API
*/
class Scan_Status extends Status {
/**
* Scan endpoint
*
* @var string
*/
const SCAN_API_BASE = '/sites/%d/scan';
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = 'jetpack_scan_status';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = 'jetpack_scan_status_timestamp';
/**
* Time in seconds that the cache should last
*
* @var int
*/
const OPTION_EXPIRES_AFTER = 300; // 5 minutes.
/**
* Gets the current status of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status( $refresh_from_wpcom = false ) {
if ( self::$status !== null ) {
return self::$status;
}
if ( $refresh_from_wpcom || ! self::should_use_cache() || self::is_cache_expired() ) {
$status = self::fetch_from_api();
} else {
$status = self::get_from_options();
}
if ( is_wp_error( $status ) ) {
$status = new Status_Model(
array(
'error' => true,
'error_code' => $status->get_error_code(),
'error_message' => $status->get_error_message(),
)
);
} else {
$status = self::normalize_api_data( $status );
}
self::$status = $status;
return $status;
}
/**
* Gets the Scan API endpoint
*
* @return WP_Error|string
*/
public static function get_api_url() {
$blog_id = Jetpack_Options::get_option( 'id' );
$is_connected = ( new Connection_Manager() )->is_connected();
if ( ! $blog_id || ! $is_connected ) {
return new WP_Error( 'site_not_connected' );
}
$api_url = sprintf( self::SCAN_API_BASE, $blog_id );
return $api_url;
}
/**
* Fetches the status data from the Scan API
*
* @return WP_Error|array
*/
public static function fetch_from_api() {
$api_url = self::get_api_url();
if ( is_wp_error( $api_url ) ) {
return $api_url;
}
$response = Client::wpcom_json_api_request_as_blog(
self::get_api_url(),
'2',
array( 'method' => 'GET' ),
null,
'wpcom'
);
$response_code = wp_remote_retrieve_response_code( $response );
if ( is_wp_error( $response ) || 200 !== $response_code || empty( $response['body'] ) ) {
return new WP_Error( 'failed_fetching_status', 'Failed to fetch Scan data from the server', array( 'status' => $response_code ) );
}
$body = json_decode( wp_remote_retrieve_body( $response ) );
self::update_status_option( $body );
return $body;
}
/**
* Normalize API Data
* Formats the payload from the Scan API into an instance of Status_Model.
*
* @param object $scan_data The data returned by the scan API.
*
* @return Status_Model
*/
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;
if ( ! empty( $scan_data->most_recent->timestamp ) ) {
$date = new \DateTime( $scan_data->most_recent->timestamp );
if ( $date ) {
$status->last_checked = $date->format( 'Y-m-d H:i:s' );
}
}
$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
)
);
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;
}
}
if ( isset( $threat->signature ) && 'Vulnerable.WP.Core' === $threat->signature ) {
if ( $threat->version !== $wp_version ) {
continue;
}
$status->core->threats[] = new Threat_Model(
array(
'id' => $threat->id,
'signature' => $threat->signature,
'title' => $threat->title,
'description' => $threat->description,
'first_detected' => $threat->first_detected,
'severity' => $threat->severity,
)
);
++$status->num_threats;
continue;
}
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;
}
}
}
$installed_plugins = Plugins_Installer::get_plugins();
$status->plugins = self::merge_installed_and_checked_lists( $installed_plugins, $status->plugins, array( 'type' => 'plugins' ), true );
$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;
}
}
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
*
* @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.
*/
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;
}
}
$new_list[] = $extension;
}
$new_list = parent::sort_threats( $new_list );
return $new_list;
}
}

View File

@ -7,6 +7,8 @@
namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Protect_Status\Status;
/**
* Site_Health.
*

View File

@ -1,306 +0,0 @@
<?php
/**
* Class to handle the Status of Jetpack Protect
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Class that handles fetching and caching the Status of vulnerabilities check from the WPCOM servers
*/
class Status {
/**
* Name of the option where status is stored
*
* @var string
*/
const OPTION_NAME = '';
/**
* Name of the option where the timestamp of the status is stored
*
* @var string
*/
const OPTION_TIMESTAMP_NAME = '';
/**
* Time in seconds that the cache should last
*
* @var int
*/
const OPTION_EXPIRES_AFTER = 3600; // 1 hour.
/**
* Time in seconds that the cache for the initial empty response should last
*
* @var int
*/
const INITIAL_OPTION_EXPIRES_AFTER = 1 * MINUTE_IN_SECONDS;
/**
* Memoization for the current status
*
* @var null|Status_Model
*/
public static $status = null;
/**
* Gets the current status of the Jetpack Protect checks
*
* @param bool $refresh_from_wpcom Refresh the local plan and status cache from wpcom.
* @return Status_Model
*/
public static function get_status( $refresh_from_wpcom = false ) {
$use_scan_status = Plan::has_required_plan();
if ( defined( 'JETPACK_PROTECT_DEV__DATA_SOURCE' ) ) {
if ( 'scan_api' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
$use_scan_status = true;
}
if ( 'protect_report' === JETPACK_PROTECT_DEV__DATA_SOURCE ) {
$use_scan_status = false;
}
}
self::$status = $use_scan_status ? Scan_Status::get_status( $refresh_from_wpcom ) : Protect_Status::get_status( $refresh_from_wpcom );
return self::$status;
}
/**
* Checks if the current cached status is expired and should be renewed
*
* @return boolean
*/
public static function is_cache_expired() {
$option_timestamp = get_option( static::OPTION_TIMESTAMP_NAME );
if ( ! $option_timestamp ) {
return true;
}
return time() > (int) $option_timestamp;
}
/**
* Checks if we should consider the stored cache or bypass it
*
* @return boolean
*/
public static function should_use_cache() {
return defined( 'JETPACK_PROTECT_DEV__BYPASS_CACHE' ) && JETPACK_PROTECT_DEV__BYPASS_CACHE ? false : true;
}
/**
* Gets the current cached status
*
* @return bool|array False if value is not found. Array with values if cache is found.
*/
public static function get_from_options() {
return maybe_unserialize( get_option( static::OPTION_NAME ) );
}
/**
* Updated the cached status and its timestamp
*
* @param array $status The new status to be cached.
* @return void
*/
public static function update_status_option( $status ) {
// TODO: Sanitize $status.
update_option( static::OPTION_NAME, maybe_serialize( $status ) );
$end_date = self::get_cache_end_date_by_status( $status );
update_option( static::OPTION_TIMESTAMP_NAME, $end_date );
}
/**
* Returns the timestamp the cache should expire depending on the current status
*
* Initial empty status, which are returned before the first check was performed, should be cache for less time
*
* @param object $status The response from the server being cached.
* @return int The timestamp when the cache should expire.
*/
public static function get_cache_end_date_by_status( $status ) {
if ( ! is_object( $status ) || empty( $status->last_checked ) ) {
return time() + static::INITIAL_OPTION_EXPIRES_AFTER;
}
return time() + static::OPTION_EXPIRES_AFTER;
}
/**
* Delete the cached status and its timestamp
*
* @return bool Whether all related status options were successfully deleted.
*/
public static function delete_option() {
$option_deleted = delete_option( static::OPTION_NAME );
$option_timestamp_deleted = delete_option( static::OPTION_TIMESTAMP_NAME );
return $option_deleted && $option_timestamp_deleted;
}
/**
* Checks the current status to see if there are any threats found
*
* @return boolean
*/
public static function has_threats() {
return 0 < self::get_total_threats();
}
/**
* Gets the total number of threats found
*
* @return integer
*/
public static function get_total_threats() {
$status = static::get_status();
return isset( $status->num_threats ) && is_int( $status->num_threats ) ? $status->num_threats : 0;
}
/**
* Get all threats combined
*
* @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()
);
}
/**
* Get threats found for WordPress core
*
* @return array
*/
public static function get_wordpress_threats() {
return self::get_threats( 'core' );
}
/**
* Get threats found for themes
*
* @return array
*/
public static function get_themes_threats() {
return self::get_threats( 'themes' );
}
/**
* Get threats found for plugins
*
* @return array
*/
public static function get_plugins_threats() {
return self::get_threats( 'plugins' );
}
/**
* Get threats found for files
*
* @return array
*/
public static function get_files_threats() {
return self::get_threats( 'files' );
}
/**
* Get threats found for plugins
*
* @return array
*/
public static function get_database_threats() {
return self::get_threats( 'database' );
}
/**
* Get the threats for one type of extension or core
*
* @param string $type What threats you want to get. Possible values are 'core', 'themes' and 'plugins'.
*
* @return array
*/
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 );
}
}
}
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;
}
/**
* Sort By Threats
*
* @param array<object> $threats Array of threats to sort.
*
* @return array<object> The sorted $threats array.
*/
protected static function sort_threats( $threats ) {
usort(
$threats,
function ( $a, $b ) {
// sort primarily based on the presence of threats
$ret = empty( $a->threats ) <=> empty( $b->threats );
// sort secondarily on whether the item has been checked
if ( ! $ret ) {
$ret = $a->checked <=> $b->checked;
}
return $ret;
}
);
return $threats;
}
}

View File

@ -9,6 +9,7 @@ namespace Automattic\Jetpack\Protect;
use Automattic\Jetpack\Connection\Client;
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
use Automattic\Jetpack\Protect_Status\Scan_Status;
use Jetpack_Options;
use WP_Error;
@ -43,7 +44,7 @@ class Threats {
* @return bool
*/
public static function update_threat( $threat_id, $updates ) {
$api_base = self::get_api_base( $threat_id );
$api_base = self::get_api_base();
if ( is_wp_error( $api_base ) ) {
return false;
}
@ -64,6 +65,7 @@ class Threats {
// clear the now out-of-date cache
Scan_Status::delete_option();
Scan_History::delete_option();
return true;
}
@ -79,6 +81,17 @@ class Threats {
return self::update_threat( $threat_id, array( 'ignore' => true ) );
}
/**
* Unignore Threat
*
* @param string $threat_id The threat ID.
*
* @return bool
*/
public static function unignore_threat( $threat_id ) {
return self::update_threat( $threat_id, array( 'unignore' => true ) );
}
/**
* Fix Threats
*
@ -112,6 +125,7 @@ class Threats {
// clear the now out-of-date cache
Scan_Status::delete_option();
Scan_History::delete_option();
$parsed_response = json_decode( $response['body'] );
@ -157,6 +171,7 @@ class Threats {
// clear the potentially out-of-date cache
Scan_Status::delete_option();
Scan_History::delete_option();
return $parsed_response;
}
@ -196,6 +211,7 @@ class Threats {
// clear the now out-of-date cache
Scan_Status::delete_option();
Scan_History::delete_option();
return true;
}

View File

@ -1,110 +0,0 @@
<?php
/**
* Model class for extensions.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for extension data.
*/
class Extension_Model {
/**
* The extension name.
*
* @var null|string
*/
public $name;
/**
* The extension slug.
*
* @var null|string
*/
public $slug;
/**
* The extension version.
*
* @var null|string
*/
public $version;
/**
* A collection of threats related to this version of the extension.
*
* @var array<Threat_Model>
*/
public $threats = array();
/**
* Whether the extension has been checked for threats.
*
* @var null|bool
*/
public $checked;
/**
* The type of extension ("plugins", "themes", or "core").
*
* @var null|string
*/
public $type;
/**
* Extension Model Constructor
*
* @param array|object $extension Extension data to load into the model instance.
*/
public function __construct( $extension = array() ) {
if ( is_object( $extension ) ) {
$extension = (array) $extension;
}
foreach ( $extension as $property => $value ) {
if ( property_exists( $this, $property ) ) {
// use the property's setter method when possible
if ( method_exists( $this, "set_$property" ) ) {
$this->{ "set_$property" }( $value );
continue;
}
// otherwise, map the value directly into the class property
$this->$property = $value;
}
}
}
/**
* Set Threats
*
* @param array<Threat_Model|array|object> $threats An array of threat data to add to the extension.
*/
public function set_threats( $threats ) {
if ( ! is_array( $threats ) ) {
$this->threats = array();
return;
}
// convert each provided threat item into an instance of Threat_Model
$threats = array_map(
function ( $threat ) {
if ( is_a( $threat, 'Threat_Model' ) ) {
return $threat;
}
if ( is_object( $threat ) ) {
$threat = (array) $threat;
}
return new Threat_Model( $threat );
},
$threats
);
$this->threats = $threats;
}
}

View File

@ -1,141 +0,0 @@
<?php
/**
* Model class for Protect status report data.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for the Protect status report data.
*/
class Status_Model {
/**
* Data source.
*
* @var string protect_report|scan_api
*/
public $data_source;
/**
* The date and time when the status was generated.
*
* @var string
*/
public $last_checked;
/**
* The number of threats.
*
* @var int
*/
public $num_threats;
/**
* The number of plugin threats.
*
* @var int
*/
public $num_plugins_threats;
/**
* The number of theme threats.
*
* @var int
*/
public $num_themes_threats;
/**
* The current report status.
*
* @var string in_progress|scheduled|idle|scanning|provisioning|unavailable
*/
public $status;
/**
* WordPress core status.
*
* @var object
*/
public $core;
/**
* Status themes.
*
* @var array<Extension_Model>
*/
public $themes = array();
/**
* Status plugins.
*
* @var array<Extension_Model>
*/
public $plugins = array();
/**
* File threats.
*
* @var array<Extension_Model>
*/
public $files = array();
/**
* Database threats.
*
* @var array<Extension_Model>
*/
public $database = array();
/**
* Whether the site includes items that have not been checked.
*
* @var boolean
*/
public $has_unchecked_items;
/**
* The estimated percentage of the current scan.
*
* @var int
*/
public $current_progress;
/**
* Whether there was an error loading the status.
*
* @var bool
*/
public $error = false;
/**
* The error code thrown when loading the status.
*
* @var string
*/
public $error_code;
/**
* The error message thrown when loading the status.
*
* @var string
*/
public $error_message;
/**
* Status constructor.
*
* @param array $status The status data to load into the class instance.
*/
public function __construct( $status = array() ) {
// set status defaults
$this->core = new \stdClass();
foreach ( $status as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
}
}
}

View File

@ -1,115 +0,0 @@
<?php
/**
* Model class for threat data.
*
* @package automattic/jetpack-protect-plugin
*/
namespace Automattic\Jetpack\Protect;
/**
* Model class for threat data.
*/
class Threat_Model {
/**
* Threat ID.
*
* @var null|string
*/
public $id;
/**
* Threat Signature.
*
* @var null|string
*/
public $signature;
/**
* Threat Title.
*
* @var null|string
*/
public $title;
/**
* Threat Description.
*
* @var null|string
*/
public $description;
/**
* The data the threat was first detected.
*
* @var null|string
*/
public $first_detected;
/**
* The version the threat is fixed in.
*
* @var null|string
*/
public $fixed_in;
/**
* The severity of the threat between 1-5.
*
* @var null|int
*/
public $severity;
/**
* Information about the auto-fix available for this threat. False when not auto-fixable.
*
* @var null|bool|object
*/
public $fixable;
/**
* The current status of the threat.
*
* @var null|string
*/
public $status;
/**
* The filename of the threat.
*
* @var null|string
*/
public $filename;
/**
* The context of the threat.
*
* @var null|object
*/
public $context;
/**
* The source URL of the threat.
*
* @var null|string
*/
public $source;
/**
* Threat Constructor
*
* @param array|object $threat Threat data to load into the class instance.
*/
public function __construct( $threat ) {
if ( is_object( $threat ) ) {
$threat = (array) $threat;
}
foreach ( $threat as $property => $value ) {
if ( property_exists( $this, $property ) ) {
$this->$property = $value;
}
}
}
}