Completed
Push — update/protect_main_site_on_ac... ( ae1898 )
by
unknown
58:17 queued 48:31
created

Jetpack_Protect_Module::modules_loaded()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4285
cc 1
eloc 5
nc 1
nop 0
1
<?php
2
/**
3
 * Module Name: Protect
4
 * Module Description: Prevent brute force attacks.
5
 * Sort Order: 1
6
 * Recommendation Order: 4
7
 * First Introduced: 3.4
8
 * Requires Connection: Yes
9
 * Auto Activate: Yes
10
 * Module Tags: Recommended
11
 * Feature: Recommended, Performance-Security
12
 * Additional Search Queries: security, secure, protection, botnet, brute force, protect, login
13
 */
14
15
include_once JETPACK__PLUGIN_DIR . 'modules/protect/shared-functions.php';
16
17
class Jetpack_Protect_Module {
18
19
	private static $__instance = null;
20
	public $api_key;
21
	public $api_key_error;
22
	public $whitelist;
23
	public $whitelist_error;
24
	public $whitelist_saved;
25
	private $user_ip;
26
	private $local_host;
27
	private $api_endpoint;
28
	public $last_request;
29
	public $last_response_raw;
30
	public $last_response;
31
	private $block_login_with_math;
32
33
	/**
34
	 * Singleton implementation
35
	 *
36
	 * @return object
37
	 */
38
	public static function instance() {
39
		if ( ! is_a( self::$__instance, 'Jetpack_Protect_Module' ) ) {
40
			self::$__instance = new Jetpack_Protect_Module();
41
		}
42
43
		return self::$__instance;
44
	}
45
46
	/**
47
	 * Registers actions
48
	 */
49
	private function __construct() {
50
		add_action( 'jetpack_activate_module_protect', array ( $this, 'on_activation' ) );
51
		add_action( 'jetpack_deactivate_module_protect', array ( $this, 'on_deactivation' ) );
52
		add_action( 'init', array ( $this, 'maybe_get_protect_key' ) );
53
		add_action( 'jetpack_modules_loaded', array ( $this, 'modules_loaded' ) );
54
		add_action( 'login_head', array ( $this, 'check_use_math' ) );
55
		add_filter( 'authenticate', array ( $this, 'check_preauth' ), 10, 3 );
56
		add_action( 'wp_login', array ( $this, 'log_successful_login' ), 10, 2 );
57
		add_action( 'wp_login_failed', array ( $this, 'log_failed_attempt' ) );
58
		add_action( 'admin_init', array ( $this, 'maybe_update_headers' ) );
59
		add_action( 'admin_init', array ( $this, 'maybe_display_security_warning' ) );
60
61
		// This is a backup in case $pagenow fails for some reason
62
		add_action( 'login_head', array ( $this, 'check_login_ability' ) );
63
64
		// Runs a script every day to clean up expired transients so they don't
65
		// clog up our users' databases
66
		require_once( JETPACK__PLUGIN_DIR . '/modules/protect/transient-cleanup.php' );
67
	}
68
69
	/**
70
	 * On module activation, try to get an api key
71
	 */
72
	public function on_activation() {
73
		if ( is_multisite() && is_main_site() && get_site_option( 'jetpack_protect_active', 0 ) == 0 ) {
74
			update_site_option( 'jetpack_protect_active', 1 );
75
		}
76
77
		update_site_option( 'jetpack_protect_activating', 'activating' );
78
79
		// Get BruteProtect's counter number
80
		Jetpack_Protect_Module::protect_call( 'check_key' );
81
	}
82
83
	/**
84
	 * On module deactivation, unset protect_active
85
	 */
86
	public function on_deactivation() {
87
		if ( is_multisite() && is_main_site() ) {
88
			update_site_option( 'jetpack_protect_active', 0 );
89
		}
90
	}
91
92
	public function maybe_get_protect_key() {
93
		if ( get_site_option( 'jetpack_protect_activating', false ) && ! get_site_option( 'jetpack_protect_key', false ) ) {
94
			$this->get_protect_key();
95
			delete_site_option( 'jetpack_protect_activating' );
96
		}
97
	}
98
99
	/**
100
	 * Sends a "check_key" API call once a day.  This call allows us to track IP-related
101
	 * headers for this server via the Protect API, in order to better identify the source
102
	 * IP for login attempts
103
	 */
104
	public function maybe_update_headers( $force = false ) {
105
		$updated_recently = $this->get_transient( 'jpp_headers_updated_recently' );
106
107
		if ( ! $force ) {
108
			if ( isset( $_GET['protect_update_headers'] ) ) {
109
				$force = true;
110
			}
111
		}
112
113
		// check that current user is admin so we prevent a lower level user from adding
114
		// a trusted header, allowing them to brute force an admin account
115
		if ( ( $updated_recently && ! $force ) || ! current_user_can( 'update_plugins' ) ) {
116
			return;
117
		}
118
119
		$response = Jetpack_Protect_Module::protect_call( 'check_key' );
120
		$this->set_transient( 'jpp_headers_updated_recently', 1, DAY_IN_SECONDS );
121
122
		if ( isset( $response['msg'] ) && $response['msg'] ) {
123
			update_site_option( 'trusted_ip_header', json_decode( $response['msg'] ) );
124
		}
125
126
	}
127
128
	public function maybe_display_security_warning() {
129
		if ( is_multisite() && current_user_can( 'manage_network' ) ) {
130
			if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
131
				require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
132
			}
133
134
			if ( ! is_plugin_active_for_network( 'jetpack/jetpack.php' ) ) {
135
				add_action( 'load-index.php', array ( $this, 'prepare_jetpack_protect_multisite_notice' ) );
136
			}
137
		}
138
	}
139
140
	public function prepare_jetpack_protect_multisite_notice() {
141
		add_action( 'admin_print_styles', array ( $this, 'admin_banner_styles' ) );
142
		add_action( 'admin_notices', array ( $this, 'admin_jetpack_manage_notice' ) );
143
	}
144
145
	public function admin_banner_styles() {
146
		global $wp_styles;
147
148
		$min = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
149
150
		wp_enqueue_style( 'jetpack', plugins_url( "css/jetpack-banners{$min}.css", JETPACK__PLUGIN_FILE ), false, JETPACK__VERSION );
151
		$wp_styles->add_data( 'jetpack', 'rtl', true );
152
	}
153
154
	public function admin_jetpack_manage_notice() {
155
156
		$dismissed = get_site_option( 'jetpack_dismissed_protect_multisite_banner' );
157
158
		if ( $dismissed ) {
159
			return;
160
		}
161
162
		$referer     = '&_wp_http_referer=' . add_query_arg( '_wp_http_referer', null );
163
		$opt_out_url = wp_nonce_url( Jetpack::admin_url( 'jetpack-notice=jetpack-protect-multisite-opt-out' . $referer ), 'jetpack_protect_multisite_banner_opt_out' );
164
165
		?>
166
		<div id="message" class="updated jetpack-message jp-banner is-opt-in protect-error"
167
		     style="display:block !important;">
168
			<a class="jp-banner__dismiss" href="<?php echo esc_url( $opt_out_url ); ?>"
169
			   title="<?php esc_attr_e( 'Dismiss this notice.', 'jetpack' ); ?>"></a>
170
171
			<div class="jp-banner__content">
172
				<h2><?php esc_html_e( 'Protect cannot keep your site secure.', 'jetpack' ); ?></h2>
173
174
				<p><?php printf( __( 'Thanks for activating Protect! To start protecting your site, please network activate Jetpack on your Multisite installation and activate Protect on your primary site. Due to the way logins are handled on WordPress Multisite, Jetpack must be network-enabled in order for Protect to work properly. <a href="%s" target="_blank">Learn More</a>', 'jetpack' ), 'http://jetpack.com/support/multisite-protect' ); ?></p>
175
			</div>
176
			<div class="jp-banner__action-container is-opt-in">
177
				<a href="<?php echo network_admin_url( 'plugins.php' ); ?>" class="jp-banner__button"
178
				   id="wpcom-connect"><?php _e( 'View Network Admin', 'jetpack' ); ?></a>
179
			</div>
180
		</div>
181
		<?php
182
	}
183
184
	/**
185
	 * Request an api key from wordpress.com
186
	 *
187
	 * @return bool | string
188
	 */
189
	public function get_protect_key() {
190
191
		$protect_blog_id = Jetpack_Protect_Module::get_main_blog_jetpack_id();
192
193
		// If we can't find the the blog id, that means we are on multisite, and the main site never connected
194
		// the protect api key is linked to the main blog id - instruct the user to connect their main blog
195
		if ( ! $protect_blog_id ) {
196
			$this->api_key_error = __( 'Your main blog is not connected to WordPress.com. Please connect to get an API key.', 'jetpack' );
197
198
			return false;
199
		}
200
201
		$request = array (
202
			'jetpack_blog_id'      => $protect_blog_id,
203
			'bruteprotect_api_key' => get_site_option( 'bruteprotect_api_key' ),
204
			'multisite'            => '0',
205
		);
206
207
		// Send the number of blogs on the network if we are on multisite
208
		if ( is_multisite() ) {
209
			$request['multisite'] = get_blog_count();
210
			if ( ! $request['multisite'] ) {
211
				global $wpdb;
212
				$request['multisite'] = $wpdb->get_var( "SELECT COUNT(blog_id) as c FROM $wpdb->blogs WHERE spam = '0' AND deleted = '0' and archived = '0'" );
213
			}
214
		}
215
216
		// Request the key
217
		Jetpack::load_xml_rpc_client();
218
		$xml = new Jetpack_IXR_Client( array (
219
			'user_id' => get_current_user_id()
220
		) );
221
		$xml->query( 'jetpack.protect.requestKey', $request );
222
223
		// Hmm, can't talk to wordpress.com
224
		if ( $xml->isError() ) {
225
			$code                = $xml->getErrorCode();
226
			$message             = $xml->getErrorMessage();
227
			$this->api_key_error = sprintf( __( 'Error connecting to WordPress.com. Code: %1$s, %2$s', 'jetpack' ), $code, $message );
228
229
			return false;
230
		}
231
232
		$response = $xml->getResponse();
233
234
		// Hmm. Can't talk to the protect servers ( api.bruteprotect.com )
235
		if ( ! isset( $response['data'] ) ) {
236
			$this->api_key_error = __( 'No reply from Jetpack servers', 'jetpack' );
237
238
			return false;
239
		}
240
241
		// There was an issue generating the key
242
		if ( empty( $response['success'] ) ) {
243
			$this->api_key_error = $response['data'];
244
245
			return false;
246
		}
247
248
		// Key generation successful!
249
		$active_plugins = Jetpack::get_active_plugins();
250
251
		// We only want to deactivate BruteProtect if we successfully get a key
252
		if ( in_array( 'bruteprotect/bruteprotect.php', $active_plugins ) ) {
253
			Jetpack_Client_Server::deactivate_plugin( 'bruteprotect/bruteprotect.php', 'BruteProtect' );
254
		}
255
256
		$key = $response['data'];
257
		update_site_option( 'jetpack_protect_key', $key );
258
259
		return $key;
260
	}
261
262
	/**
263
	 * Called via WP action wp_login_failed to log failed attempt with the api
264
	 *
265
	 * Fires custom, plugable action jpp_log_failed_attempt with the IP
266
	 *
267
	 * @return void
268
	 */
269
	function log_failed_attempt() {
270
		/**
271
		 * Fires before every failed login attempt.
272
		 *
273
		 * @module protect
274
		 *
275
		 * @since 3.4.0
276
		 *
277
		 * @param string jetpack_protect_get_ip IP stored by Protect.
278
		 */
279
		do_action( 'jpp_log_failed_attempt', jetpack_protect_get_ip() );
280
281
		if ( isset( $_COOKIE['jpp_math_pass'] ) ) {
282
283
			$transient = $this->get_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
284
			$transient--;
285
286
			if ( ! $transient || $transient < 1 ) {
287
				$this->delete_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'] );
288
				setcookie( 'jpp_math_pass', 0, time() - DAY_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false );
289
			} else {
290
				$this->set_transient( 'jpp_math_pass_' . $_COOKIE['jpp_math_pass'], $transient, DAY_IN_SECONDS );
291
			}
292
293
		}
294
		$this->protect_call( 'failed_attempt' );
295
	}
296
297
	/**
298
	 * Set up the Protect configuration page
299
	 */
300
	public function modules_loaded() {
301
		Jetpack::enable_module_configurable( __FILE__ );
302
		Jetpack::module_configuration_load( __FILE__, array ( $this, 'configuration_load' ) );
303
		Jetpack::module_configuration_head( __FILE__, array ( $this, 'configuration_head' ) );
304
		Jetpack::module_configuration_screen( __FILE__, array ( $this, 'configuration_screen' ) );
305
	}
306
307
	/**
308
	 * Logs a successful login back to our servers, this allows us to make sure we're not blocking
309
	 * a busy IP that has a lot of good logins along with some forgotten passwords. Also saves current user's ip
310
	 * to the ip address whitelist
311
	 */
312
	public function log_successful_login( $user_login, $user ) {
313
		$this->protect_call( 'successful_login', array ( 'roles' => $user->roles ) );
314
	}
315
316
317
	/**
318
	 * Checks for loginability BEFORE authentication so that bots don't get to go around the log in form.
319
	 *
320
	 * If we are using our math fallback, authenticate via math-fallback.php
321
	 *
322
	 * @param string $user
323
	 * @param string $username
324
	 * @param string $password
325
	 *
326
	 * @return string $user
327
	 */
328
	function check_preauth( $user = 'Not Used By Protect', $username = 'Not Used By Protect', $password = 'Not Used By Protect' ) {
329
		$allow_login = $this->check_login_ability( true );
330
		$use_math    = $this->get_transient( 'brute_use_math' );
331
332
		if ( ! $allow_login ) {
333
			$this->block_with_math();
334
		}
335
336
		if ( ( 1 == $use_math || 1 == $this->block_login_with_math ) && isset( $_POST['log'] ) ) {
337
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
338
			Jetpack_Protect_Math_Authenticate::math_authenticate();
339
		}
340
341
		return $user;
342
	}
343
344
	/**
345
	 * Get all IP headers so that we can process on our server...
346
	 *
347
	 * @return string
348
	 */
349
	function get_headers() {
350
		$ip_related_headers = array (
351
			'GD_PHP_HANDLER',
352
			'HTTP_AKAMAI_ORIGIN_HOP',
353
			'HTTP_CF_CONNECTING_IP',
354
			'HTTP_CLIENT_IP',
355
			'HTTP_FASTLY_CLIENT_IP',
356
			'HTTP_FORWARDED',
357
			'HTTP_FORWARDED_FOR',
358
			'HTTP_INCAP_CLIENT_IP',
359
			'HTTP_TRUE_CLIENT_IP',
360
			'HTTP_X_CLIENTIP',
361
			'HTTP_X_CLUSTER_CLIENT_IP',
362
			'HTTP_X_FORWARDED',
363
			'HTTP_X_FORWARDED_FOR',
364
			'HTTP_X_IP_TRAIL',
365
			'HTTP_X_REAL_IP',
366
			'HTTP_X_VARNISH',
367
			'REMOTE_ADDR'
368
		);
369
370
		foreach ( $ip_related_headers as $header ) {
371
			if ( isset( $_SERVER[ $header ] ) ) {
372
				$output[ $header ] = $_SERVER[ $header ];
0 ignored issues
show
Coding Style Comprehensibility introduced by
$output was never initialized. Although not strictly required by PHP, it is generally a good practice to add $output = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
373
			}
374
		}
375
376
		return $output;
0 ignored issues
show
Bug introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
377
	}
378
379
	/*
380
	 * Checks if the IP address has been whitelisted
381
	 *
382
	 * @param string $ip
383
	 *
384
	 * @return bool
385
	 */
386
	function ip_is_whitelisted( $ip ) {
387
		// If we found an exact match in wp-config
388
		if ( defined( 'JETPACK_IP_ADDRESS_OK' ) && JETPACK_IP_ADDRESS_OK == $ip ) {
389
			return true;
390
		}
391
392
		$whitelist = jetpack_protect_get_local_whitelist();
393
394
		if ( is_multisite() ) {
395
			$whitelist = array_merge( $whitelist, get_site_option( 'jetpack_protect_global_whitelist', array () ) );
396
		}
397
398
		if ( ! empty( $whitelist ) ) :
399
			foreach ( $whitelist as $item ) :
400
				// If the IPs are an exact match
401
				if ( ! $item->range && isset( $item->ip_address ) && $item->ip_address == $ip ) {
402
					return true;
403
				}
404
405
				if ( $item->range && isset( $item->range_low ) && isset( $item->range_high ) ) {
406
					if ( jetpack_protect_ip_address_is_in_range( $ip, $item->range_low, $item->range_high ) ) {
407
						return true;
408
					}
409
				}
410
			endforeach;
411
		endif;
412
413
		return false;
414
	}
415
416
	/**
417
	 * Checks the status for a given IP. API results are cached as transients
418
	 *
419
	 * @param bool $preauth Whether or not we are checking prior to authorization
420
	 *
421
	 * @return bool Either returns true, fires $this->kill_login, or includes a math fallback and returns false
422
	 */
423
	function check_login_ability( $preauth = false ) {
424
		$headers         = $this->get_headers();
425
		$header_hash     = md5( json_encode( $headers ) );
426
		$transient_name  = 'jpp_li_' . $header_hash;
427
		$transient_value = $this->get_transient( $transient_name );
428
		$ip              = jetpack_protect_get_ip();
429
430
		if ( jetpack_protect_ip_is_private( $ip ) ) {
431
			return true;
432
		}
433
434
		if ( $this->ip_is_whitelisted( $ip ) ) {
435
			return true;
436
		}
437
438
		// Check out our transients
439
		if ( isset( $transient_value ) && 'ok' == $transient_value['status'] ) {
440
			return true;
441
		}
442
443
		if ( isset( $transient_value ) && 'blocked' == $transient_value['status'] ) {
444
			$this->block_with_math();
445
		}
446
447
		if ( isset( $transient_value ) && 'blocked-hard' == $transient_value['status'] ) {
448
			$this->kill_login();
449
		}
450
451
		// If we've reached this point, this means that the IP isn't cached.
452
		// Now we check with the Protect API to see if we should allow login
453
		$response = $this->protect_call( $action = 'check_ip' );
454
455
		if ( isset( $response['math'] ) && ! function_exists( 'brute_math_authenticate' ) ) {
456
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
457
			new Jetpack_Protect_Math_Authenticate;
458
459
			return false;
460
		}
461
462
		if ( 'blocked' == $response['status'] ) {
463
			$this->block_with_math();
464
		}
465
466
		if ( 'blocked-hard' == $response['status'] ) {
467
			$this->kill_login();
468
		}
469
470
		return true;
471
	}
472
473
	function block_with_math() {
474
		/**
475
		 * By default, Protect will allow a user who has been blocked for too
476
		 * many failed logins to start answering math questions to continue logging in
477
		 *
478
		 * For added security, you can disable this.
479
		 *
480
		 * @module protect
481
		 *
482
		 * @since 3.6.0
483
		 *
484
		 * @param bool Whether to allow math for blocked users or not.
485
		 */
486
487
		$this->block_login_with_math = 1;
488
		/**
489
		 * Allow Math fallback for blocked IPs.
490
		 *
491
		 * @module protect
492
		 *
493
		 * @since 3.6.0
494
		 *
495
		 * @param bool true Should we fallback to the Math questions when an IP is blocked. Default to true.
496
		 */
497
		$allow_math_fallback_on_fail = apply_filters( 'jpp_use_captcha_when_blocked', true );
498
		if ( ! $allow_math_fallback_on_fail ) {
499
			$this->kill_login();
500
		}
501
		include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
502
		new Jetpack_Protect_Math_Authenticate;
503
504
		return false;
505
	}
506
507
	/*
508
	 * Kill a login attempt
509
	 */
510
	function kill_login() {
511
		$ip = jetpack_protect_get_ip();
512
		/**
513
		 * Fires before every killed login.
514
		 *
515
		 * @module protect
516
		 *
517
		 * @since 3.4.0
518
		 *
519
		 * @param string $ip IP flagged by Protect.
520
		 */
521
		do_action( 'jpp_kill_login', $ip );
522
		$help_url = 'http://jetpack.com/support/security/';
523
524
		$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.  <a href="%2$s">Find out more...</a>', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ), esc_url( $help_url ) );
525
526
		if( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) {
527
			$die_string = sprintf( __( 'Your IP (%1$s) has been flagged for potential security violations.', 'jetpack' ), str_replace( 'http://', '', esc_url( 'http://' . $ip ) ) );
528
		}
529
530
		wp_die(
531
			$die_string,
532
			__( 'Login Blocked by Jetpack', 'jetpack' ),
533
			array ( 'response' => 403 )
534
		);
535
	}
536
537
	/*
538
	 * Checks if the protect API call has failed, and if so initiates the math captcha fallback.
539
	 */
540
	public function check_use_math() {
541
		$use_math = $this->get_transient( 'brute_use_math' );
542
		if ( $use_math ) {
543
			include_once dirname( __FILE__ ) . '/protect/math-fallback.php';
544
			new Jetpack_Protect_Math_Authenticate;
545
		}
546
	}
547
548
	/**
549
	 * Get or delete API key
550
	 */
551
	public function configuration_load() {
552
553
		if ( isset( $_POST['action'] ) && $_POST['action'] == 'jetpack_protect_save_whitelist' && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
554
			$whitelist             = str_replace( ' ', '', $_POST['whitelist'] );
555
			$whitelist             = explode( PHP_EOL, $whitelist );
556
			$result                = jetpack_protect_save_whitelist( $whitelist );
557
			$this->whitelist_saved = ! is_wp_error( $result );
558
			$this->whitelist_error = is_wp_error( $result );
559
		}
560
561
		if ( isset( $_POST['action'] ) && 'get_protect_key' == $_POST['action'] && wp_verify_nonce( $_POST['_wpnonce'], 'jetpack-protect' ) ) {
562
			$result = $this->get_protect_key();
563
			// Only redirect on success
564
			// If it fails we need access to $this->api_key_error
565
			if ( $result ) {
566
				wp_safe_redirect( Jetpack::module_configuration_url( 'protect' ) );
567
			}
568
		}
569
570
		$this->api_key = get_site_option( 'jetpack_protect_key', false );
571
		$this->user_ip = jetpack_protect_get_ip();
572
	}
573
574
	public function configuration_head() {
575
		wp_enqueue_style( 'jetpack-protect' );
576
	}
577
578
	/**
579
	 * Prints the configuration screen
580
	 */
581
	public function configuration_screen() {
582
		require_once dirname( __FILE__ ) . '/protect/config-ui.php';
583
	}
584
585
	/**
586
	 * If we're in a multisite network, return the blog ID of the primary blog
587
	 *
588
	 * @return int
589
	 */
590
	public function get_main_blog_id() {
591
		if ( ! is_multisite() ) {
592
			return false;
593
		}
594
595
		global $current_site;
596
		$primary_blog_id = $current_site->blog_id;
597
598
		return $primary_blog_id;
599
	}
600
601
	/**
602
	 * Get jetpack blog id, or the jetpack blog id of the main blog in the main network
603
	 *
604
	 * @return int
605
	 */
606
	public function get_main_blog_jetpack_id() {
607
		if ( ! is_main_site() ) {
608
			switch_to_blog( $this->get_main_blog_id() );
609
			$id = Jetpack::get_option( 'id', false );
610
			restore_current_blog();
611
		} else {
612
			$id = Jetpack::get_option( 'id' );
613
		}
614
615
		return $id;
616
	}
617
618
	public function check_api_key() {
619
		$response = $this->protect_call( 'check_key' );
620
621
		if ( isset( $response['ckval'] ) ) {
622
			return true;
623
		}
624
625
		if ( isset( $response['error'] ) ) {
626
627
			if ( $response['error'] == 'Invalid API Key' ) {
628
				$this->api_key_error = __( 'Your API key is invalid', 'jetpack' );
629
			}
630
631
			if ( $response['error'] == 'API Key Required' ) {
632
				$this->api_key_error = __( 'No API key', 'jetpack' );
633
			}
634
		}
635
636
		$this->api_key_error = __( 'There was an error contacting Jetpack servers.', 'jetpack' );
637
638
		return false;
639
	}
640
641
	/**
642
	 * Calls over to the api using wp_remote_post
643
	 *
644
	 * @param string $action 'check_ip', 'check_key', or 'failed_attempt'
645
	 * @param array $request Any custom data to post to the api
646
	 *
647
	 * @return array
648
	 */
649
	function protect_call( $action = 'check_ip', $request = array () ) {
650
		global $wp_version, $wpdb, $current_user;
651
652
		$api_key = get_site_option( 'jetpack_protect_key' );
653
654
		$user_agent = "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' );
655
656
		$request['action']            = $action;
657
		$request['ip']                = jetpack_protect_get_ip();
658
		$request['host']              = $this->get_local_host();
659
		$request['headers']           = json_encode( $this->get_headers() );
660
		$request['jetpack_version']   = constant( 'JETPACK__VERSION' );
661
		$request['wordpress_version'] = strval( $wp_version );
662
		$request['api_key']           = $api_key;
663
		$request['multisite']         = "0";
664
665
		if ( is_multisite() ) {
666
			$request['multisite'] = get_blog_count();
667
		}
668
669
		$args = array (
670
			'body'        => $request,
671
			'user-agent'  => $user_agent,
672
			'httpversion' => '1.0',
673
			'timeout'     => 15
674
		);
675
676
		$response_json           = wp_remote_post( $this->get_api_host(), $args );
677
		$this->last_response_raw = $response_json;
678
		$headers                 = $this->get_headers();
679
		$header_hash             = md5( json_encode( $headers ) );
680
		$transient_name          = 'jpp_li_' . $header_hash;
681
		$this->delete_transient( $transient_name );
682
683
		if ( is_array( $response_json ) ) {
684
			$response = json_decode( $response_json['body'], true );
685
		}
686
687
		if ( isset( $response['blocked_attempts'] ) && $response['blocked_attempts'] ) {
688
			update_site_option( 'jetpack_protect_blocked_attempts', $response['blocked_attempts'] );
689
		}
690
691
		if ( isset( $response['status'] ) && ! isset( $response['error'] ) ) {
692
			$response['expire'] = time() + $response['seconds_remaining'];
693
			$this->set_transient( $transient_name, $response, $response['seconds_remaining'] );
694
			$this->delete_transient( 'brute_use_math' );
695
		} else { // Fallback to Math Captcha if no response from API host
696
			$this->set_transient( 'brute_use_math', 1, 600 );
697
			$response['status'] = 'ok';
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
698
			$response['math']   = true;
699
		}
700
701
		if ( isset( $response['error'] ) ) {
702
			update_site_option( 'jetpack_protect_error', $response['error'] );
703
		} else {
704
			delete_site_option( 'jetpack_protect_error' );
705
		}
706
707
		return $response;
708
	}
709
710
711
	/**
712
	 * Wrapper for WordPress set_transient function, our version sets
713
	 * the transient on the main site in the network if this is a multisite network
714
	 *
715
	 * We do it this way (instead of set_site_transient) because of an issue where
716
	 * sitewide transients are always autoloaded
717
	 * https://core.trac.wordpress.org/ticket/22846
718
	 *
719
	 * @param string $transient Transient name. Expected to not be SQL-escaped. Must be
720
	 *                           45 characters or fewer in length.
721
	 * @param mixed $value Transient value. Must be serializable if non-scalar.
722
	 *                           Expected to not be SQL-escaped.
723
	 * @param int $expiration Optional. Time until expiration in seconds. Default 0.
724
	 *
725
	 * @return bool False if value was not set and true if value was set.
726
	 */
727
	function set_transient( $transient, $value, $expiration ) {
728
		if ( is_multisite() && ! is_main_site() ) {
729
			switch_to_blog( $this->get_main_blog_id() );
730
			$return = set_transient( $transient, $value, $expiration );
731
			restore_current_blog();
732
733
			return $return;
734
		}
735
736
		return set_transient( $transient, $value, $expiration );
737
	}
738
739
	/**
740
	 * Wrapper for WordPress delete_transient function, our version deletes
741
	 * the transient on the main site in the network if this is a multisite network
742
	 *
743
	 * @param string $transient Transient name. Expected to not be SQL-escaped.
744
	 *
745
	 * @return bool true if successful, false otherwise
746
	 */
747 View Code Duplication
	function delete_transient( $transient ) {
748
		if ( is_multisite() && ! is_main_site() ) {
749
			switch_to_blog( $this->get_main_blog_id() );
750
			$return = delete_transient( $transient );
751
			restore_current_blog();
752
753
			return $return;
754
		}
755
756
		return delete_transient( $transient );
757
	}
758
759
	/**
760
	 * Wrapper for WordPress get_transient function, our version gets
761
	 * the transient on the main site in the network if this is a multisite network
762
	 *
763
	 * @param string $transient Transient name. Expected to not be SQL-escaped.
764
	 *
765
	 * @return mixed Value of transient.
766
	 */
767 View Code Duplication
	function get_transient( $transient ) {
768
		if ( is_multisite() && ! is_main_site() ) {
769
			switch_to_blog( $this->get_main_blog_id() );
770
			$return = get_transient( $transient );
771
			restore_current_blog();
772
773
			return $return;
774
		}
775
776
		return get_transient( $transient );
777
	}
778
779
	function get_api_host() {
780
		if ( isset( $this->api_endpoint ) ) {
781
			return $this->api_endpoint;
782
		}
783
784
		//Check to see if we can use SSL
785
		$this->api_endpoint = Jetpack::fix_url_for_bad_hosts( JETPACK_PROTECT__API_HOST );
786
787
		return $this->api_endpoint;
788
	}
789
790
	function get_local_host() {
791
		if ( isset( $this->local_host ) ) {
792
			return $this->local_host;
793
		}
794
795
		$uri = 'http://' . strtolower( $_SERVER['HTTP_HOST'] );
796
797
		if ( is_multisite() ) {
798
			$uri = network_home_url();
799
		}
800
801
		$uridata = parse_url( $uri );
802
803
		$domain = $uridata['host'];
804
805
		// If we still don't have the site_url, get it
806
		if ( ! $domain ) {
807
			$uri     = get_site_url( 1 );
808
			$uridata = parse_url( $uri );
809
			$domain  = $uridata['host'];
810
		}
811
812
		$this->local_host = $domain;
813
814
		return $this->local_host;
815
	}
816
817
}
818
819
Jetpack_Protect_Module::instance();
820
821
if ( isset( $pagenow ) && 'wp-login.php' == $pagenow ) {
822
	Jetpack_Protect_Module::check_login_ability();
823
}
824