Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 172
0.00% covered (danger)
0.00%
0 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Protect
0.00% covered (danger)
0.00%
0 / 170
0.00% covered (danger)
0.00%
0 / 21
1332
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
2
 init
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 admin_page_init
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 enqueue_admin_styles
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 enqueue_admin_scripts
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 render_initial_state
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initial_state
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_settings_page
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_activation
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 do_plugin_activation_activities
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 activate_modules
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 plugin_deactivation
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 admin_bar
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 protect_filter_available_modules
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 jetpack_check_user_licenses
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
56
 get_waf_upgrade_seen_status
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_waf_upgrade_seen_status
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 get_waf_upgrade_badge_timestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 set_waf_upgrade_badge_timestamp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_waf_upgrade_badge_display_status
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 get_waf_stats
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Primary class file for the Jetpack Protect plugin.
4 *
5 * @package automattic/jetpack-protect-plugin
6 */
7
8if ( ! defined( 'ABSPATH' ) ) {
9    exit( 0 );
10}
11
12use Automattic\Jetpack\Account_Protection\Settings as Account_Protection_Settings;
13use Automattic\Jetpack\Admin_UI\Admin_Menu;
14use Automattic\Jetpack\Assets;
15use Automattic\Jetpack\Connection\Initial_State as Connection_Initial_State;
16use Automattic\Jetpack\Connection\Manager as Connection_Manager;
17use Automattic\Jetpack\IP\Utils as IP_Utils;
18use Automattic\Jetpack\JITMS\JITM;
19use Automattic\Jetpack\Modules;
20use Automattic\Jetpack\My_Jetpack\Initializer as My_Jetpack_Initializer;
21use Automattic\Jetpack\My_Jetpack\Products as My_Jetpack_Products;
22use Automattic\Jetpack\Plugins_Installer;
23use Automattic\Jetpack\Protect\Credentials;
24use Automattic\Jetpack\Protect\Onboarding;
25use Automattic\Jetpack\Protect\REST_Controller;
26use Automattic\Jetpack\Protect\Scan_History;
27use Automattic\Jetpack\Protect\Site_Health;
28use Automattic\Jetpack\Protect\Threats;
29use Automattic\Jetpack\Protect_Status\Plan;
30use Automattic\Jetpack\Protect_Status\Protect_Status;
31use Automattic\Jetpack\Protect_Status\Scan_Status;
32use Automattic\Jetpack\Protect_Status\Status;
33use Automattic\Jetpack\Status as Jetpack_Status;
34use Automattic\Jetpack\Sync\Functions as Sync_Functions;
35use Automattic\Jetpack\Sync\Sender;
36use Automattic\Jetpack\Waf\Waf_Runner;
37use Automattic\Jetpack\Waf\Waf_Stats;
38
39/**
40 * Class Jetpack_Protect
41 *
42 * @phan-constructor-used-for-side-effects
43 */
44class Jetpack_Protect {
45
46    const JETPACK_SCAN_PRODUCT_IDS                   = array(
47        2010, // JETPACK_SECURITY_DAILY.
48        2011, // JETPACK_SECURITY_DAILY_MOTNHLY.
49        2012, // JETPACK_SECURITY_REALTIME.
50        2013, // JETPACK_SECURITY_REALTIME_MONTHLY.
51        2014, // JETPACK_COMPLETE.
52        2015, // JETPACK_COMPLETE_MONTHLY.
53        2016, // JETPACK_SECURITY_TIER_1_YEARLY.
54        2017, // JETPACK_SECURITY_TIER_1_MONTHLY.
55        2019, // JETPACK_SECURITY_TIER_2_YEARLY.
56        2020, // JETPACK_SECURITY_TIER_2_MONTHLY.
57        2106, // JETPACK_SCAN.
58        2107, // JETPACK_SCAN_MONTHLY.
59        2108, // JETPACK_SCAN_REALTIME.
60        2109, // JETPACK_SCAN_REALTIME_MONTHLY.
61    );
62    const JETPACK_WAF_MODULE_SLUG                    = 'waf';
63    const JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG = 'protect';
64    const JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG     = 'account-protection';
65    const JETPACK_PROTECT_ACTIVATION_OPTION          = JETPACK_PROTECT_SLUG . '_activated';
66
67    /**
68     * Constructor.
69     */
70    public function __construct() {
71        add_action( 'init', array( $this, 'init' ) );
72        add_action( '_admin_menu', array( $this, 'admin_page_init' ) );
73
74        // Activate the module as the plugin is activated
75        add_action( 'admin_init', array( $this, 'do_plugin_activation_activities' ) );
76
77        // Init Jetpack packages
78        add_action(
79            'plugins_loaded',
80            function () {
81                $config = new Automattic\Jetpack\Config();
82                // Connection package.
83                $config->ensure(
84                    'connection',
85                    array(
86                        'slug'     => JETPACK_PROTECT_SLUG,
87                        'name'     => JETPACK_PROTECT_NAME,
88                        'url_info' => JETPACK_PROTECT_URI,
89                    )
90                );
91                // Sync package.
92                $config->ensure(
93                    'sync',
94                    array(
95                        'jetpack_sync_modules'             => array(
96                            'Automattic\\Jetpack\\Sync\\Modules\\Options',
97                            'Automattic\\Jetpack\\Sync\\Modules\\Callables',
98                            'Automattic\\Jetpack\\Sync\\Modules\\Users',
99                        ),
100                        'jetpack_sync_callable_whitelist'  => array(
101                            'main_network_site' => array( 'Automattic\\Jetpack\\Connection\\Urls', 'main_network_site_url' ),
102                            'get_plugins'       => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_plugins' ),
103                            'get_themes'        => array( 'Automattic\\Jetpack\\Sync\\Functions', 'get_themes' ),
104                            'wp_version'        => array( 'Automattic\\Jetpack\\Sync\\Functions', 'wp_version' ),
105                        ),
106                        'jetpack_sync_options_contentless' => array(),
107                        'jetpack_sync_options_whitelist'   => array(
108                            'active_plugins',
109                            'stylesheet',
110                        ),
111                    )
112                );
113
114                // Identity crisis package.
115                $config->ensure( 'identity_crisis' );
116
117                // Web application firewall package.
118                $config->ensure( 'waf' );
119
120                // Account protection package.
121                $config->ensure( 'account_protection' );
122            },
123            1
124        );
125
126        add_filter( 'jetpack_connection_user_has_license', array( $this, 'jetpack_check_user_licenses' ), 10, 3 );
127
128        add_filter( 'jetpack_get_available_standalone_modules', array( $this, 'protect_filter_available_modules' ), 10, 1 );
129    }
130
131    /**
132     * Initialize the plugin
133     *
134     * @return void
135     */
136    public function init() {
137        add_action( 'admin_bar_menu', array( $this, 'admin_bar' ), 65 );
138        add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_styles' ) );
139
140        REST_Controller::init();
141        My_Jetpack_Initializer::init();
142        Site_Health::init();
143
144        // Sets up JITMS.
145        JITM::configure();
146    }
147
148    /**
149     * Initialize the admin page resources.
150     */
151    public function admin_page_init() {
152        $total_threats = Status::get_total_threats();
153        $menu_label    = _x( 'Protect', 'The Jetpack Protect product name, without the Jetpack prefix', 'jetpack-protect' );
154        if ( $total_threats ) {
155            $menu_label .= sprintf( ' <span class="update-plugins">%d</span>', $total_threats );
156        }
157
158        $page_suffix = Admin_Menu::add_menu(
159            __( 'Jetpack Protect', 'jetpack-protect' ),
160            $menu_label,
161            'manage_options',
162            'jetpack-protect',
163            array( $this, 'plugin_settings_page' ),
164            5
165        );
166
167        add_action( 'load-' . $page_suffix, array( $this, 'enqueue_admin_scripts' ) );
168    }
169
170    /**
171     * Enqueues the wp-admin styles (used outside the React app)
172     */
173    public function enqueue_admin_styles() {
174        wp_enqueue_style( 'jetpack-protect-wpadmin', JETPACK_PROTECT_BASE_PLUGIN_URL . '/assets/jetpack-protect.css', array(), JETPACK_PROTECT_VERSION );
175    }
176
177    /**
178     * Enqueue plugin admin scripts and styles.
179     */
180    public function enqueue_admin_scripts() {
181
182        Assets::register_script(
183            'jetpack-protect',
184            'build/index.js',
185            JETPACK_PROTECT_ROOT_FILE,
186            array(
187                'in_footer'  => true,
188                'textdomain' => 'jetpack-protect',
189            )
190        );
191        Assets::enqueue_script( 'jetpack-protect' );
192        // Required for Analytics.
193        wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
194        // Initial JS state including JP Connection data.
195        Connection_Initial_State::render_script( 'jetpack-protect' );
196        wp_add_inline_script( 'jetpack-protect', $this->render_initial_state(), 'before' );
197    }
198
199    /**
200     * Render the initial state into a JavaScript variable.
201     *
202     * @return string
203     */
204    public function render_initial_state() {
205        return 'var jetpackProtectInitialState=' . wp_json_encode( $this->initial_state(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
206    }
207
208    /**
209     * Get the initial state data for hydrating the React UI.
210     *
211     * @return array
212     */
213    public function initial_state() {
214        global $wp_version;
215
216        // Always fetch the latest plan status from WPCOM.
217        $has_plan = Plan::has_required_plan( true );
218
219        $status = Status::get_status();
220
221        $initial_state = array(
222            'apiRoot'            => esc_url_raw( rest_url() ),
223            'apiNonce'           => wp_create_nonce( 'wp_rest' ),
224            'registrationNonce'  => wp_create_nonce( 'jetpack-registration-nonce' ),
225            'credentials'        => Credentials::get_credential_array(),
226            'status'             => $status,
227            'fixerStatus'        => Threats::fix_threats_status( $status->fixable_threat_ids ),
228            'scanHistory'        => Scan_History::get_scan_history(),
229            'installedPlugins'   => Plugins_Installer::get_plugins(),
230            'installedThemes'    => Sync_Functions::get_themes(),
231            'wpVersion'          => $wp_version,
232            'adminUrl'           => 'admin.php?page=jetpack-protect',
233            'siteSuffix'         => ( new Jetpack_Status() )->get_site_suffix(),
234            'blogID'             => Connection_Manager::get_site_id( true ),
235            'jetpackScan'        => My_Jetpack_Products::get_product( 'scan' ),
236            'hasPlan'            => $has_plan,
237            'onboardingProgress' => Onboarding::get_current_user_progress(),
238            'accountProtection'  => ( new Account_Protection_Settings() )->get(),
239            'waf'                => array(
240                'wafSupported'        => Waf_Runner::is_supported_environment(),
241                'currentIp'           => IP_Utils::get_ip(),
242                'upgradeIsSeen'       => self::get_waf_upgrade_seen_status(),
243                'displayUpgradeBadge' => self::get_waf_upgrade_badge_display_status(),
244                'isEnabled'           => Waf_Runner::is_enabled(),
245                'config'              => Waf_Runner::get_config(),
246                'stats'               => self::get_waf_stats(),
247                'globalStats'         => Waf_Stats::get_global_stats(),
248            ),
249        );
250
251        $initial_state['jetpackScan']['pricingForUi'] = Plan::get_product( 'jetpack_scan' );
252
253        return $initial_state;
254    }
255    /**
256     * Main plugin settings page.
257     */
258    public function plugin_settings_page() {
259        ?>
260            <div id="jetpack-protect-root"></div>
261        <?php
262    }
263
264    /**
265     * Activate the WAF module on plugin activation.
266     *
267     * @static
268     */
269    public static function plugin_activation() {
270        add_option( self::JETPACK_PROTECT_ACTIVATION_OPTION, true );
271    }
272
273    /**
274     * Runs on admin_init, and does actions required on plugin activation, based on
275     * the activation option.
276     *
277     * This needs to be run after the activation hook, as that results in a redirect,
278     * and we need the sync module's actions and filters to be registered.
279     */
280    public static function do_plugin_activation_activities() {
281        if ( get_option( self::JETPACK_PROTECT_ACTIVATION_OPTION ) && ( new Connection_Manager() )->is_connected() ) {
282            self::activate_modules();
283        }
284    }
285
286    /**
287     * Activates the waf and brute force protection modules and disables the activation option
288     */
289    public static function activate_modules() {
290        delete_option( self::JETPACK_PROTECT_ACTIVATION_OPTION );
291        ( new Modules() )->activate( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, false, false );
292        ( new Modules() )->activate( self::JETPACK_WAF_MODULE_SLUG, false, false );
293        ( new Modules() )->activate( self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG, false, false );
294    }
295
296    /**
297     * Removes plugin from the connection manager
298     * If it's the last plugin using the connection, the site will be disconnected.
299     *
300     * @access public
301     * @static
302     */
303    public static function plugin_deactivation() {
304
305        // Clear Sync data.
306        Sender::get_instance()->uninstall();
307
308        $manager = new Connection_Manager( 'jetpack-protect' );
309        $manager->remove_connection();
310
311        Protect_Status::delete_option();
312        Scan_Status::delete_option();
313        Scan_History::delete_option();
314    }
315
316    /**
317     * Create a shortcut on Admin Bar to show the total of threats found.
318     *
319     * @param object $wp_admin_bar The Admin Bar object.
320     * @return void
321     */
322    public function admin_bar( $wp_admin_bar ) {
323        if ( ! current_user_can( 'manage_options' ) ) {
324            return;
325        }
326
327        $total = Status::get_total_threats();
328
329        if ( $total > 0 ) {
330            $args = array(
331                'id'    => 'jetpack-protect',
332                'title' => '<span class="ab-icon jp-protect-icon"></span><span class="ab-label">' . $total . '</span>',
333                'href'  => admin_url( 'admin.php?page=jetpack-protect' ),
334                'meta'  => array(
335                    // translators: %d is the number of threats found.
336                    'title' => sprintf( _n( '%d threat found by Jetpack Protect', '%d threats found by Jetpack Protect', $total, 'jetpack-protect' ), $total ),
337                ),
338            );
339
340            $wp_admin_bar->add_node( $args );
341        }
342    }
343
344    /**
345     * Adds modules to the list of available modules
346     *
347     * @param array $modules The available modules.
348     * @return array
349     */
350    public function protect_filter_available_modules( $modules ) {
351        return array_merge( array( self::JETPACK_ACCOUNT_PROTECTION_MODULE_SLUG, self::JETPACK_WAF_MODULE_SLUG, self::JETPACK_BRUTE_FORCE_PROTECTION_MODULE_SLUG ), $modules );
352    }
353
354    /**
355     * Check if the user has an available license that includes Jetpack Scan.
356     *
357     * @param boolean  $has_license  Whether a license was already found.
358     * @param object[] $licenses     Unattached licenses belonging to the user.
359     * @param string   $plugin_slug  Slug of the plugin that initiated the flow.
360     *
361     * @return boolean
362     */
363    public static function jetpack_check_user_licenses( $has_license, $licenses, $plugin_slug ) {
364        if ( $plugin_slug !== JETPACK_PROTECT_SLUG || $has_license ) {
365            return $has_license;
366        }
367
368        $license_found = false;
369
370        foreach ( $licenses as $license ) {
371            if ( $license->attached_at || $license->revoked_at ) {
372                continue;
373            }
374
375            if ( in_array( $license->product_id, self::JETPACK_SCAN_PRODUCT_IDS, true ) ) {
376                $license_found = true;
377                break;
378            }
379        }
380
381        return $license_found;
382    }
383
384    /**
385     * Get WAF Upgrade "Seen" Status
386     *
387     * @return bool Whether the current user has dismissed the upgrade popover or enabled the automatic rules feature.
388     */
389    public static function get_waf_upgrade_seen_status() {
390        return (bool) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true );
391    }
392
393    /**
394     * Set WAF Upgrade "Seen" Status
395     *
396     * @return bool True if upgrade seen status updated to true, false on failure.
397     */
398    public static function set_waf_upgrade_seen_status() {
399        self::set_waf_upgrade_badge_timestamp();
400        return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_seen', true );
401    }
402
403    /**
404     * Get WAF Upgrade Badge Timestamp
405     *
406     * @return integer The timestamp for the when the upgrade seen status was first set to true.
407     */
408    public static function get_waf_upgrade_badge_timestamp() {
409        return (int) get_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', true );
410    }
411
412    /**
413     * Set WAF Upgrade Badge Timestamp
414     *
415     * @return bool True if upgrade badge timestamp to set to the current time, false on failure.
416     */
417    public static function set_waf_upgrade_badge_timestamp() {
418        return (bool) update_user_meta( get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp', time() );
419    }
420
421    /**
422     * Get WAF Upgrade Badge Display Status
423     *
424     * @return bool True if upgrade badge timestamp is set and less than 7 days ago, otherwise false.
425     */
426    public static function get_waf_upgrade_badge_display_status() {
427        $badge_timestamp_exists = metadata_exists( 'user', get_current_user_id(), 'jetpack_protect_waf_upgrade_badge_timestamp' );
428        if ( ! $badge_timestamp_exists ) {
429            return true;
430        }
431
432        $badge_timestamp = self::get_waf_upgrade_badge_timestamp();
433        $seven_days      = strtotime( '-7 days' );
434        if ( $badge_timestamp > $seven_days ) {
435            return true;
436        }
437
438        return false;
439    }
440
441    /**
442     * Get WAF stats
443     *
444     * @return bool|array False if WAF is not enabled, otherwise an array of stats.
445     */
446    public static function get_waf_stats() {
447        if ( ! Waf_Runner::is_enabled() ) {
448            return false;
449        }
450
451        return array(
452            'blockedRequests'           => Plan::has_required_plan() ? Waf_Stats::get_blocked_requests() : false,
453            'automaticRulesLastUpdated' => Waf_Stats::get_automatic_rules_last_updated(),
454        );
455    }
456}