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