467 lines
14 KiB
PHP
467 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Primary class file for the Jetpack Protect plugin.
|
|
*
|
|
* @package automattic/jetpack-protect-plugin
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
use Automattic\Jetpack\Admin_UI\Admin_Menu;
|
|
use Automattic\Jetpack\Assets;
|
|
use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
|
|
use Automattic\Jetpack\Connection\Manager as Connection_Manager;
|
|
use Automattic\Jetpack\IP\Utils as IP_Utils;
|
|
use Automattic\Jetpack\JITMS\JITM;
|
|
use Automattic\Jetpack\Modules;
|
|
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\REST_Controller;
|
|
use Automattic\Jetpack\Protect\Scan_History;
|
|
use Automattic\Jetpack\Protect\Site_Health;
|
|
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;
|
|
use Automattic\Jetpack\Waf\Waf_Runner;
|
|
use Automattic\Jetpack\Waf\Waf_Stats;
|
|
|
|
/**
|
|
* Class Jetpack_Protect
|
|
*/
|
|
class Jetpack_Protect {
|
|
|
|
/**
|
|
* Licenses product ID.
|
|
*
|
|
* @var string
|
|
*/
|
|
const JETPACK_SCAN_PRODUCT_IDS = array(
|
|
2010, // JETPACK_SECURITY_DAILY.
|
|
2011, // JETPACK_SECURITY_DAILY_MOTNHLY.
|
|
2012, // JETPACK_SECURITY_REALTIME.
|
|
2013, // JETPACK_SECURITY_REALTIME_MONTHLY.
|
|
2014, // JETPACK_COMPLETE.
|
|
2015, // JETPACK_COMPLETE_MONTHLY.
|
|
2016, // JETPACK_SECURITY_TIER_1_YEARLY.
|
|
2017, // JETPACK_SECURITY_TIER_1_MONTHLY.
|
|
2019, // JETPACK_SECURITY_TIER_2_YEARLY.
|
|
2020, // JETPACK_SECURITY_TIER_2_MONTHLY.
|
|
2106, // JETPACK_SCAN.
|
|
2107, // JETPACK_SCAN_MONTHLY.
|
|
2108, // JETPACK_SCAN_REALTIME.
|
|
2109, // JETPACK_SCAN_REALTIME_MONTHLY.
|
|
);
|
|
const JETPACK_WAF_MODULE_SLUG = 'waf';
|
|
const JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG = 'protect';
|
|
const JETPACK_PROTECT_ACTIVATION_OPTION = JETPACK_PROTECT_SLUG . '_activated';
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'init', array( $this, 'init' ) );
|
|
add_action( '_admin_menu', array( $this, 'admin_page_init' ) );
|
|
|
|
// Activate the module as the plugin is activated
|
|
add_action( 'admin_init', array( $this, 'do_plugin_activation_activities' ) );
|
|
|
|
// Init Jetpack packages
|
|
add_action(
|
|
'plugins_loaded',
|
|
function () {
|
|
$config = new Automattic\Jetpack\Config();
|
|
// Connection package.
|
|
$config->ensure(
|
|
'connection',
|
|
array(
|
|
'slug' => JETPACK_PROTECT_SLUG,
|
|
'name' => JETPACK_PROTECT_NAME,
|
|
'url_info' => JETPACK_PROTECT_URI,
|
|
)
|
|
);
|
|
// Sync package.
|
|
$config->ensure(
|
|
'sync',
|
|
array(
|
|
'jetpack_sync_modules' => array(
|
|
'Automattic\\Jetpack\\Sync\\Modules\\Options',
|
|
'Automattic\\Jetpack\\Sync\\Modules\\Callables',
|
|
'Automattic\\Jetpack\\Sync\\Modules\\Users',
|
|
),
|
|
'jetpack_sync_callable_whitelist' => array(
|
|
'main_network_site' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'main_network_site_url' ),
|
|
'get_plugins' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
|
|
'get_themes' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
|
|
'wp_version' => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
|
|
),
|
|
'jetpack_sync_options_contentless' => array(),
|
|
'jetpack_sync_options_whitelist' => array(
|
|
'active_plugins',
|
|
'stylesheet',
|
|
),
|
|
)
|
|
);
|
|
|
|
// Identity crisis package.
|
|
$config->ensure( 'identity_crisis' );
|
|
|
|
// Web application firewall package.
|
|
$config->ensure( 'waf' );
|
|
},
|
|
1
|
|
);
|
|
|
|
add_filter( 'jetpack_connection_user_has_license', array( $this, 'jetpack_check_user_licenses' ), 10, 3 );
|
|
|
|
add_filter( 'jetpack_get_available_standalone_modules', array( $this, 'protect_filter_available_modules' ), 10, 1 );
|
|
}
|
|
|
|
/**
|
|
* Initialize the plugin
|
|
*
|
|
* @return void
|
|
*/
|
|
public function init() {
|
|
add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 65 );
|
|
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
|
|
|
|
REST_Controller::init();
|
|
My_Jetpack_Initializer::init();
|
|
Site_Health::init();
|
|
|
|
// Sets up JITMS.
|
|
JITM::configure();
|
|
}
|
|
|
|
/**
|
|
* Initialize the admin page resources.
|
|
*/
|
|
public function admin_page_init() {
|
|
$total_threats = Status::get_total_threats();
|
|
$menu_label = _x( 'Protect', 'The Jetpack Protect product name, without the Jetpack prefix', 'jetpack-protect' );
|
|
if ( $total_threats ) {
|
|
$menu_label .= sprintf( ' <span class="update-plugins">%d</span>', $total_threats );
|
|
}
|
|
|
|
$page_suffix = Admin_Menu::add_menu(
|
|
__( 'Jetpack Protect', 'jetpack-protect' ),
|
|
$menu_label,
|
|
'manage_options',
|
|
'jetpack-protect',
|
|
array( $this, 'plugin_settings_page' ),
|
|
5
|
|
);
|
|
|
|
add_action( 'load-' . $page_suffix, array( $this, 'enqueue_admin_scripts' ) );
|
|
}
|
|
|
|
/**
|
|
* Enqueues the wp-admin styles (used outside the React app)
|
|
*/
|
|
public function enqueue_admin_styles() {
|
|
wp_enqueue_style( 'jetpack-protect-wpadmin', JETPACK_PROTECT_BASE_PLUGIN_URL . '/assets/jetpack-protect.css', array(), JETPACK_PROTECT_VERSION );
|
|
}
|
|
|
|
/**
|
|
* Enqueue plugin admin scripts and styles.
|
|
*/
|
|
public function enqueue_admin_scripts() {
|
|
|
|
Assets::register_script(
|
|
'jetpack-protect',
|
|
'build/index.js',
|
|
JETPACK_PROTECT_ROOT_FILE,
|
|
array(
|
|
'in_footer' => true,
|
|
'textdomain' => 'jetpack-protect',
|
|
)
|
|
);
|
|
Assets::enqueue_script( 'jetpack-protect' );
|
|
// Required for Analytics.
|
|
wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
|
|
// Initial JS state including JP Connection data.
|
|
Connection_Initial_State::render_script( 'jetpack-protect' );
|
|
wp_add_inline_script( 'jetpack-protect', $this->render_initial_state(), 'before' );
|
|
}
|
|
|
|
/**
|
|
* Render the initial state into a JavaScript variable.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function render_initial_state() {
|
|
return 'var jetpackProtectInitialState=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $this->initial_state() ) ) . '"));';
|
|
}
|
|
|
|
/**
|
|
* Get the initial state data for hydrating the React UI.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function initial_state() {
|
|
global $wp_version;
|
|
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
|
$refresh_status_from_wpcom = isset( $_GET['checkPlan'] );
|
|
$initial_state = array(
|
|
'apiRoot' => esc_url_raw( rest_url() ),
|
|
'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,
|
|
'adminUrl' => 'admin.php?page=jetpack-protect',
|
|
'siteSuffix' => ( new Jetpack_Status() )->get_site_suffix(),
|
|
'blogID' => Connection_Manager::get_site_id( true ),
|
|
'jetpackScan' => My_Jetpack_Products::get_product( 'scan' ),
|
|
'hasRequiredPlan' => Plan::has_required_plan(),
|
|
'onboardingProgress' => Onboarding::get_current_user_progress(),
|
|
'waf' => array(
|
|
'wafSupported' => Waf_Runner::is_supported_environment(),
|
|
'currentIp' => IP_Utils::get_ip(),
|
|
'isSeen' => self::get_waf_seen_status(),
|
|
'upgradeIsSeen' => self::get_waf_upgrade_seen_status(),
|
|
'displayUpgradeBadge' => self::get_waf_upgrade_badge_display_status(),
|
|
'isEnabled' => Waf_Runner::is_enabled(),
|
|
'isToggling' => false,
|
|
'isUpdating' => false,
|
|
'config' => Waf_Runner::get_config(),
|
|
'stats' => self::get_waf_stats(),
|
|
'globalStats' => Waf_Stats::get_global_stats(),
|
|
),
|
|
);
|
|
|
|
$initial_state['jetpackScan']['pricingForUi'] = Plan::get_product( 'jetpack_scan' );
|
|
|
|
return $initial_state;
|
|
}
|
|
/**
|
|
* Main plugin settings page.
|
|
*/
|
|
public function plugin_settings_page() {
|
|
?>
|
|
<div id="jetpack-protect-root"></div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Activate the WAF module on plugin activation.
|
|
*
|
|
* @static
|
|
*/
|
|
public static function plugin_activation() {
|
|
add_option( self::JETPACK_PROTECT_ACTIVATION_OPTION, true );
|
|
}
|
|
|
|
/**
|
|
* Runs on admin_init, and does actions required on plugin activation, based on
|
|
* the activation option.
|
|
*
|
|
* This needs to be run after the activation hook, as that results in a redirect,
|
|
* and we need the sync module's actions and filters to be registered.
|
|
*/
|
|
public static function do_plugin_activation_activities() {
|
|
if ( get_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ) && ( new Connection_Manager() )->is_connected() ) {
|
|
self::activate_modules();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Activates the waf and brute force protection modules and disables the activation option
|
|
*/
|
|
public static function activate_modules() {
|
|
delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION );
|
|
( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false );
|
|
( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false );
|
|
}
|
|
|
|
/**
|
|
* Removes plugin from the connection manager
|
|
* If it's the last plugin using the connection, the site will be disconnected.
|
|
*
|
|
* @access public
|
|
* @static
|
|
*/
|
|
public static function plugin_deactivation() {
|
|
|
|
// Clear Sync data.
|
|
Sender::get_instance()->uninstall();
|
|
|
|
$manager = new Connection_Manager( 'jetpack-protect' );
|
|
$manager->remove_connection();
|
|
|
|
Protect_Status::delete_option();
|
|
Scan_Status::delete_option();
|
|
Scan_History::delete_option();
|
|
}
|
|
|
|
/**
|
|
* Create a shortcut on Admin Bar to show the total of threats found.
|
|
*
|
|
* @param object $wp_admin_bar The Admin Bar object.
|
|
* @return void
|
|
*/
|
|
public function admin_bar( $wp_admin_bar ) {
|
|
if ( ! current_user_can( 'manage_options' ) ) {
|
|
return;
|
|
}
|
|
|
|
$total = Status::get_total_threats();
|
|
|
|
if ( $total > 0 ) {
|
|
$args = array(
|
|
'id' => 'jetpack-protect',
|
|
'title' => '<span class="ab-icon jp-protect-icon"></span><span class="ab-label">' . $total . '</span>',
|
|
'href' => admin_url( 'admin.php?page=jetpack-protect' ),
|
|
'meta' => array(
|
|
// translators: %d is the number of threats found.
|
|
'title' => sprintf( _n( '%d threat found by Jetpack Protect', '%d threats found by Jetpack Protect', $total, 'jetpack-protect' ), $total ),
|
|
),
|
|
);
|
|
|
|
$wp_admin_bar->add_node( $args );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds modules to the list of available modules
|
|
*
|
|
* @param array $modules The available modules.
|
|
* @return array
|
|
*/
|
|
public function protect_filter_available_modules( $modules ) {
|
|
return array_merge( array( self::JETPACK_WAF_MODULE_SLUG, self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG ), $modules );
|
|
}
|
|
|
|
/**
|
|
* Check if the user has an available license that includes Jetpack Scan.
|
|
*
|
|
* @param boolean $has_license Whether a license was already found.
|
|
* @param object[] $licenses Unattached licenses belonging to the user.
|
|
* @param string $plugin_slug Slug of the plugin that initiated the flow.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public static function jetpack_check_user_licenses( $has_license, $licenses, $plugin_slug ) {
|
|
if ( $plugin_slug !== JETPACK_PROTECT_SLUG || $has_license ) {
|
|
return $has_license;
|
|
}
|
|
|
|
$license_found = false;
|
|
|
|
foreach ( $licenses as $license ) {
|
|
if ( $license->attached_at || $license->revoked_at ) {
|
|
continue;
|
|
}
|
|
|
|
if ( in_array( $license->product_id, self::JETPACK_SCAN_PRODUCT_IDS, true ) ) {
|
|
$license_found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $license_found;
|
|
}
|
|
|
|
/**
|
|
* Get WAF "Seen" Status
|
|
*
|
|
* @return bool Whether the current user has viewed the WAF screen.
|
|
*/
|
|
public static function get_waf_seen_status() {
|
|
return (bool) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_seen', true );
|
|
}
|
|
|
|
/**
|
|
* Set WAF "Seen" Status
|
|
*
|
|
* @return bool True if seen status updated to true, false on failure.
|
|
*/
|
|
public static function set_waf_seen_status() {
|
|
return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_seen', true );
|
|
}
|
|
|
|
/**
|
|
* Get WAF Upgrade "Seen" Status
|
|
*
|
|
* @return bool Whether the current user has dismissed the upgrade popover or enabled the automatic rules feature.
|
|
*/
|
|
public static function get_waf_upgrade_seen_status() {
|
|
return (bool) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true );
|
|
}
|
|
|
|
/**
|
|
* Set WAF Upgrade "Seen" Status
|
|
*
|
|
* @return bool True if upgrade seen status updated to true, false on failure.
|
|
*/
|
|
public static function set_waf_upgrade_seen_status() {
|
|
self::set_waf_upgrade_badge_timestamp();
|
|
return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true );
|
|
}
|
|
|
|
/**
|
|
* Get WAF Upgrade Badge Timestamp
|
|
*
|
|
* @return integer The timestamp for the when the upgrade seen status was first set to true.
|
|
*/
|
|
public static function get_waf_upgrade_badge_timestamp() {
|
|
return (int) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', true );
|
|
}
|
|
|
|
/**
|
|
* Set WAF Upgrade Badge Timestamp
|
|
*
|
|
* @return bool True if upgrade badge timestamp to set to the current time, false on failure.
|
|
*/
|
|
public static function set_waf_upgrade_badge_timestamp() {
|
|
return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', time() );
|
|
}
|
|
|
|
/**
|
|
* Get WAF Upgrade Badge Display Status
|
|
*
|
|
* @return bool True if upgrade badge timestamp is set and less than 7 days ago, otherwise false.
|
|
*/
|
|
public static function get_waf_upgrade_badge_display_status() {
|
|
$badge_timestamp_exists = metadata_exists( 'user', get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp' );
|
|
if ( ! $badge_timestamp_exists ) {
|
|
return true;
|
|
}
|
|
|
|
$badge_timestamp = self::get_waf_upgrade_badge_timestamp();
|
|
$seven_days = strtotime( '-7 days' );
|
|
if ( $badge_timestamp > $seven_days ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get WAF stats
|
|
*
|
|
* @return bool|array False if WAF is not enabled, otherwise an array of stats.
|
|
*/
|
|
public static function get_waf_stats() {
|
|
if ( ! Waf_Runner::is_enabled() ) {
|
|
return false;
|
|
}
|
|
|
|
return array(
|
|
'ipAllowListCount' => Waf_Stats::get_ip_allow_list_count(),
|
|
'ipBlockListCount' => Waf_Stats::get_ip_block_list_count(),
|
|
'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(),
|
|
);
|
|
}
|
|
}
|