Completed
Pull Request — update/grunt-to-gulp (#3647)
by
unknown
09:16
created

Jetpack_SSO::request_initial_nonce()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 13
Ratio 100 %
Metric Value
dl 13
loc 13
rs 9.4285
cc 2
eloc 8
nc 2
nop 0
1
<?php
2
3
/**
4
 * Module Name: Single Sign On
5
 * Module Description: Secure user authentication.
6
 * Jumpstart Description: Lets you log in to all your Jetpack-enabled sites with one click using your WordPress.com account.
7
 * Sort Order: 30
8
 * Recommendation Order: 5
9
 * First Introduced: 2.6
10
 * Requires Connection: Yes
11
 * Auto Activate: No
12
 * Module Tags: Developers
13
 * Feature: Jumpstart, Performance-Security
14
 * Additional Search Queries: sso, single sign on, login, log in
15
 */
16
17
class Jetpack_SSO {
18
	static $instance = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $instance.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
19
20
	private function __construct() {
21
22
		self::$instance = $this;
23
24
		add_action( 'admin_init',  array( $this, 'admin_init' ) );
25
		add_action( 'admin_init',  array( $this, 'register_settings' ) );
26
		add_action( 'login_init',  array( $this, 'login_init' ) );
27
		add_action( 'delete_user', array( $this, 'delete_connection_for_user' ) );
28
		add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
29
		add_action( 'init', array( $this, 'maybe_logout_user' ), 5 );
30
		add_action( 'jetpack_modules_loaded', array( $this, 'module_configure_button' ) );
31
32
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
33
		add_action( 'login_form_jetpack-sso', '__return_true' );
34
35
		if (
36
			$this->should_hide_login_form() &&
37
			/**
38
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
39
			 *
40
			 * @module sso
41
			 *
42
			 * @since 2.8.0
43
			 *
44
			 * @param bool true Should the disclaimer be displayed. Default to true.
45
			 */
46
			apply_filters( 'jetpack_sso_display_disclaimer', true )
47
		) {
48
			add_action( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
49
		}
50
	}
51
52
	/**
53
	 * Returns the single instance of the Jetpack_SSO object
54
	 *
55
	 * @since 2.8
56
	 * @return Jetpack_SSO
57
	 **/
58
	public static function get_instance() {
59
		if( !is_null( self::$instance ) )
60
			return self::$instance;
61
62
		return self::$instance = new Jetpack_SSO;
63
	}
64
65
	/**
66
	 * Add configure button and functionality to the module card on the Jetpack screen
67
	 **/
68
	public static function module_configure_button() {
69
		Jetpack::enable_module_configurable( __FILE__ );
70
		Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
71
		Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
72
		Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
73
	}
74
75
	public static function module_configuration_load() {
76
		// wp_safe_redirect( admin_url( 'options-general.php#configure-sso' ) );
77
		// exit;
78
	}
79
80
	public static function module_configuration_head() {}
81
82
	public static function module_configuration_screen() {
83
		?>
84
		<form method="post" action="options.php">
85
			<?php settings_fields( 'jetpack-sso' ); ?>
86
			<?php do_settings_sections( 'jetpack-sso' ); ?>
87
			<?php submit_button(); ?>
88
		</form>
89
		<?php
90
	}
91
92
	/**
93
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
94
	 * to logout and reauthenticate with the site.
95
	 **/
96
	public function maybe_logout_user() {
97
		global $current_user;
98
99
		if( 1 == $current_user->jetpack_force_logout ) {
100
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
101
			self::delete_connection_for_user( $current_user->ID );
102
			wp_logout();
103
			wp_safe_redirect( wp_login_url() );
104
		}
105
	}
106
107
108
	/**
109
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
110
	 *
111
	 * @param array $methods
112
	 * @return array
113
	 **/
114
	public function xmlrpc_methods( $methods ) {
115
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
116
		return $methods;
117
	}
118
119
	/**
120
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
121
	 * the next time the user visits the site.
122
	 **/
123
	public function xmlrpc_user_disconnect( $user_id ) {
124
		$user_query = new WP_User_Query(
125
			array(
126
				'meta_key' => 'wpcom_user_id',
127
				'meta_value' => $user_id
128
			)
129
		);
130
		$user = $user_query->get_results();
131
		$user = $user[0];
132
133
134
		if( $user instanceof WP_User ) {
0 ignored issues
show
Bug introduced by
The class WP_User does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
135
			$user = wp_set_current_user( $user->ID );
136
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
137
			self::delete_connection_for_user( $user->ID );
138
			return true;
139
		}
140
		return false;
141
	}
142
143
	/**
144
	 * Adds settings fields to Settings > General > Single Sign On that allows users to
145
	 * turn off the login form on wp-login.php
146
	 *
147
	 * @since 2.7
148
	 **/
149
	public function register_settings() {
150
151
		add_settings_section(
152
			'jetpack_sso_settings',
153
			__( 'Single Sign On' , 'jetpack' ),
154
			'__return_false',
155
			'jetpack-sso'
156
		);
157
158
		/*
159
		 * Settings > General > Single Sign On
160
		 * Checkbox for Remove default login form
161
		 */
162
		 /* Hide in 2.9
163
		register_setting(
164
			'general',
165
			'jetpack_sso_remove_login_form',
166
			array( $this, 'validate_settings_remove_login_form_checkbox' )
167
		);
168
169
		add_settings_field(
170
			'jetpack_sso_remove_login_form',
171
			__( 'Remove default login form?' , 'jetpack' ),
172
			array( $this, 'render_remove_login_form_checkbox' ),
173
			'general',
174
			'jetpack_sso_settings'
175
		);
176
		*/
177
178
		/*
179
		 * Settings > General > Single Sign On
180
		 * Require two step authentication
181
		 */
182
		register_setting(
183
			'jetpack-sso',
184
			'jetpack_sso_require_two_step',
185
			array( $this, 'validate_jetpack_sso_require_two_step' )
186
		);
187
188
		add_settings_field(
189
			'jetpack_sso_require_two_step',
190
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
191
			array( $this, 'render_require_two_step' ),
192
			'jetpack-sso',
193
			'jetpack_sso_settings'
194
		);
195
196
197
		/*
198
		 * Settings > General > Single Sign On
199
		 */
200
		register_setting(
201
			'jetpack-sso',
202
			'jetpack_sso_match_by_email',
203
			array( $this, 'validate_jetpack_sso_match_by_email' )
204
		);
205
206
		add_settings_field(
207
			'jetpack_sso_match_by_email',
208
			'', // __( 'Match by Email' , 'jetpack' ),
209
			array( $this, 'render_match_by_email' ),
210
			'jetpack-sso',
211
			'jetpack_sso_settings'
212
		);
213
	}
214
215
	/**
216
	 * Builds the display for the checkbox allowing user to require two step
217
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
218
	 *
219
	 * @since 2.7
220
	 **/
221 View Code Duplication
	public function render_require_two_step() {
222
		echo '<label>';
223
		echo '<input type="checkbox" name="jetpack_sso_require_two_step" ' . checked( 1 == get_option( 'jetpack_sso_require_two_step' ), true, false ) . '> ';
224
		esc_html_e( 'Require Two-Step Authentication' , 'jetpack' );
225
		echo '</label>';
226
	}
227
228
	/**
229
	 * Validate the require  two step checkbox in Settings > General
230
	 *
231
	 * @since 2.7
232
	 * @return boolean
233
	 **/
234
	public function validate_jetpack_sso_require_two_step( $input ) {
235
		return ( ! empty( $input ) ) ? 1 : 0;
236
	}
237
238
	/**
239
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
240
	 * Displays in Settings > General
241
	 *
242
	 * @since 2.9
243
	 **/
244 View Code Duplication
	public function render_match_by_email() {
245
		echo '<label>';
246
		echo '<input type="checkbox" name="jetpack_sso_match_by_email"' . checked( 1 == get_option( 'jetpack_sso_match_by_email' ), true, false) . '> ';
247
		esc_html_e( 'Match by Email', 'jetpack' );
248
		echo '</label>';
249
	}
250
251
	/**
252
	 * Validate the match by email check in Settings > General
253
	 *
254
	 * @since 2.9
255
	 * @return boolean
256
	 **/
257
	public function validate_jetpack_sso_match_by_email( $input ) {
258
		return ( ! empty( $input ) ) ? 1 : 0;
259
	}
260
261
	/**
262
	 * Builds the display for the checkbox allowing users to remove the default
263
	 * WordPress login form from wp-login.php. Displays in Settings > General
264
	 *
265
	 * @since 2.7
266
	 **/
267
	public function render_remove_login_form_checkbox() {
268
		if( $this->is_user_connected( get_current_user_id() ) ) {
269
			echo '<a name="configure-sso"></a>';
270
			echo '<input type="checkbox" name="jetpack_sso_remove_login_form[remove_login_form]" ' . checked( 1 == get_option( 'jetpack_sso_remove_login_form' ), true, false ) . '>';
271
			echo '<p class="description">Removes default login form and disallows login via POST</p>';
272
		} else {
273
			echo 'Your account must be connected to WordPress.com before disabling the login form.';
274
			echo '<br/>' . $this->button();
275
		}
276
	}
277
278
	/**
279
	 * Validate settings input from Settings > General
280
	 *
281
	 * @since 2.7
282
	 * @return boolean
283
	 **/
284
	public function validate_settings_remove_login_form_checkbox( $input ) {
285
		return ( isset( $input['remove_login_form'] ) )? 1: 0;
286
	}
287
288
	/**
289
	 * Removes 'Lost your password?' text from the login form if user
290
	 * does not want to show the login form
291
	 *
292
	 * @since 2.7
293
	 * @return string
294
	 **/
295
	public function remove_lost_password_text( $text ) {
296
		if( 'Lost your password?' == $text )
297
			$text = '';
298
		return $text;
299
	}
300
301
	/**
302
	 * Checks to determine if the user wants to login on wp-login
303
	 *
304
	 * This function mostly exists to cover the exceptions to login
305
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
306
	 * does not have to exist. By default WordPress assumes login if an action
307
	 * is not set, however this may not be true, as in the case of logout
308
	 * where $_GET[loggedout] is instead set
309
	 *
310
	 * @return boolean
311
	 **/
312
	private function wants_to_login() {
313
		$wants_to_login = false;
314
315
		// Cover default WordPress behavior
316
		$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : 'login';
317
318
		// And now the exceptions
319
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
320
321
		if( 'login' == $action ) {
322
			$wants_to_login = true;
323
		}
324
325
		return $wants_to_login;
326
	}
327
328
	private function bypass_login_forward_wpcom() {
329
		/**
330
		 * Redirect the site's log in form to WordPress.com's log in form.
331
		 *
332
		 * @module sso
333
		 *
334
		 * @since 3.1.0
335
		 *
336
		 * @param bool false Should the site's log in form be automatically forwarded to WordPress.com's log in form.
337
		 */
338
		return apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
339
	}
340
341
	function login_init() {
342
		global $action;
343
344
		/**
345
 		 * If the user is attempting to logout AND the auto-forward to WordPress.com
346
 		 * login is set then we need to ensure we do not auto-forward the user and get
347
 		 * them stuck in an infinite logout loop.
348
 		 */
349
 		if( isset( $_GET['loggedout'] ) && $this->bypass_login_forward_wpcom() ) {
350
 			add_filter( 'jetpack_remove_login_form', '__return_true' );
351
 			add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
352
		}
353
354
		/**
355
		 * Check to see if the site admin wants to automagically forward the user
356
		 * to the WordPress.com login page AND  that the request to wp-login.php
357
		 * is not something other than login (Like logout!)
358
		 */
359
		if (
360
			$this->wants_to_login()
361
			&& $this->bypass_login_forward_wpcom()
362
		) {
363
			add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
364
			$this->maybe_save_cookie_redirect();
365
			wp_safe_redirect( $this->build_sso_url() );
366
		}
367
368
		if ( 'login' === $action ) {
369
			wp_enqueue_script( 'jquery' );
370
			wp_enqueue_style( 'genericons' );
371
			add_action( 'login_footer', array( $this, 'login_form' ) );
372
			add_action( 'login_footer', array( $this, 'login_footer' ) );
373
/*
374
			if ( get_option( 'jetpack_sso_remove_login_form' ) ) {
375
				// Check to see if the user is attempting to login via the default login form.
376
				// If so we need to deny it and forward elsewhere.
377
				if( isset( $_REQUEST['wp-submit'] ) && 'Log In' == $_REQUEST['wp-submit'] ) {
378
					wp_die( 'Login not permitted by this method. ');
379
				}
380
				add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
381
			}
382
*/
383
		} elseif ( 'jetpack-sso' === $action ) {
384
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
385
				$this->handle_login();
386
				wp_enqueue_script( 'jquery' );
387
				wp_enqueue_style( 'genericons' );
388
				add_action( 'login_footer', array( $this, 'login_form' ) );
389
				add_action( 'login_footer', array( $this, 'login_footer' ) );
390
			} else {
391
				if ( Jetpack::check_identity_crisis() ) {
392
					wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
393
				} else {
394
					$this->maybe_save_cookie_redirect();
395
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
396
					add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
397
					wp_safe_redirect( $this->build_sso_url() );
398
				}
399
			}
400
		}
401
	}
402
403
	/**
404
	 * Conditionally save the redirect_to url as a cookie.
405
	 */
406
	public static function maybe_save_cookie_redirect() {
407
		if ( headers_sent() ) {
408
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
409
		}
410
411
		// If we have something to redirect to
412
		if ( ! empty( $_GET['redirect_to'] ) ) {
413
			$url = esc_url_raw( $_GET['redirect_to'] );
414
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
415
		// Otherwise, if it's already set
416
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
417
			// Purge it.
418
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
419
		}
420
421
		if ( ! empty( $_GET['rememberme'] ) ) {
422
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
423
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
424
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
425
		}
426
	}
427
428
	/**
429
	 * Determine if the login form should be hidden or not
430
	 *
431
	 * Method is private only because it is only used in this class so far.
432
	 * Feel free to change it later
433
	 *
434
	 * @return bool
435
	 **/
436
	private function should_hide_login_form() {
437
		/**
438
		 * Remove the default log in form, only leave the WordPress.com log in button.
439
		 *
440
		 * @module sso
441
		 *
442
		 * @since 3.1.0
443
		 *
444
		 * @param bool get_option( 'jetpack_sso_remove_login_form', false ) Should the default log in form be removed. Default to false.
445
		 */
446
		return apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
447
	}
448
449
	function login_form() {
450
		$classes = '';
451
452
		if( $this->should_hide_login_form() ) {
453
			$classes .= ' forced-sso';
454
		}
455
		echo '<div class="jetpack-sso-wrap' . $classes . '">' . $this->button() . '</div>';
456
	}
457
458
	function login_footer() {
459
		$hide_login_form = $this->should_hide_login_form();
460
		?>
461
		<style>
462
			#loginform {
463
				overflow: hidden;
464
				padding-bottom: 26px;
465
			}
466
			.jetpack-sso-wrap {
467
				<?php if ( $hide_login_form ) : ?>
468
					text-align: center;
469
				<?php else : ?>
470
					float: right;
471
				<?php endif; ?>
472
				margin: 1em 0 0;
473
				clear: right;
474
				display: block;
475
			}
476
477
			<?php if ( $hide_login_form ) : ?>
478
			.forced-sso .jetpack-sso.button {
479
				font-size: 16px;
480
				line-height: 27px;
481
				height: 37px;
482
				padding: 5px 12px 6px 47px;
483
			}
484
			.forced-sso .jetpack-sso.button:before {
485
				font-size: 28px !important;
486
				height: 37px;
487
				padding: 5px 5px 4px;
488
				width: 37px;
489
			}
490
			<?php endif; ?>
491
		</style>
492
		<script>
493
			jQuery(document).ready(function($){
494
			<?php if ( $hide_login_form ) : ?>
495
				$( '#loginform' ).empty();
496
			<?php endif; ?>
497
				$( '#loginform' ).append( $( '.jetpack-sso-wrap' ) );
498
499
				var $rememberme = $( '#rememberme' ),
500
					$ssoButton  = $( 'a.jetpack-sso.button' );
501
502
				$rememberme.on( 'change', function() {
503
					var url       = $ssoButton.prop( 'href' ),
504
						isChecked = $rememberme.prop( 'checked' ) ? 1 : 0;
505
506
					if ( url.match( /&rememberme=\d/ ) ) {
507
						url = url.replace( /&rememberme=\d/, '&rememberme=' + isChecked );
508
					} else {
509
						url += '&rememberme=' + isChecked;
510
					}
511
512
					$ssoButton.prop( 'href', url );
513
				} ).change();
514
515
			});
516
		</script>
517
		<?php
518
	}
519
520 View Code Duplication
	static function delete_connection_for_user( $user_id ) {
521
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
522
			return;
523
		}
524
		Jetpack::load_xml_rpc_client();
525
		$xml = new Jetpack_IXR_Client( array(
526
			'user_id' => $user_id
527
		) );
528
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
529
530
		if ( $xml->isError() ) {
531
			return false;
532
		}
533
534
		return $xml->getResponse();
535
	}
536
537 View Code Duplication
	static function request_initial_nonce() {
538
		Jetpack::load_xml_rpc_client();
539
		$xml = new Jetpack_IXR_Client( array(
540
			'user_id' => get_current_user_id()
541
		) );
542
		$xml->query( 'jetpack.sso.requestNonce' );
543
544
		if ( $xml->isError() ) {
545
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
546
		}
547
548
		return $xml->getResponse();
549
	}
550
551
	/**
552
	 * The function that actually handles the login!
553
	 */
554
	function handle_login() {
555
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
556
		$wpcom_user_id = (int) $_GET['user_id'];
557
		$result        = sanitize_key( $_GET['result'] );
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
558
559
		Jetpack::load_xml_rpc_client();
560
		$xml = new Jetpack_IXR_Client( array(
561
			'user_id' => get_current_user_id()
562
		) );
563
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
564
565
		if ( $xml->isError() ) {
566
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
567
		}
568
569
		$user_data = $xml->getResponse();
570
571
		if ( empty( $user_data ) ) {
572
			wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
573
		}
574
575
		$user_data = (object) $user_data;
576
		$user = null;
577
578
		/**
579
		 * Fires before Jetpack's SSO modifies the log in form.
580
		 *
581
		 * @module sso
582
		 *
583
		 * @since 2.6.0
584
		 *
585
		 * @param object $user_data User login information.
586
		 */
587
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
588
589
		/**
590
		 * Is it required to have 2-step authentication enabled on WordPress.com to use SSO?
591
		 *
592
		 * @module sso
593
		 *
594
		 * @since 2.8.0
595
		 *
596
		 * @param bool get_option( 'jetpack_sso_require_two_step' ) Does SSO require 2-step authentication?
597
		 */
598
		$require_two_step = apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) );
599
		if( $require_two_step && 0 == (int) $user_data->two_step_enabled ) {
600
			$this->user_data = $user_data;
0 ignored issues
show
Bug introduced by
The property user_data does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
601
			/** This filter is documented in core/src/wp-includes/pluggable.php */
602
			do_action( 'wp_login_failed', $user_data->login );
603
			add_action( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
604
			return;
605
		}
606
607
		if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
608
			list( $state, $nonce ) = explode( '|', $_GET['state'] );
609
610
			if ( wp_verify_nonce( $nonce, $state ) ) {
611
				if ( 'sso-link-user' == $state ) {
612
					$user = wp_get_current_user();
613
					update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
614
					add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
615
				}
616
			} else wp_nonce_ays();
617
		}
618
619
		if ( empty( $user ) ) {
620
			$user = $this->get_user_by_wpcom_id( $user_data->ID );
621
		}
622
623
		// If we don't have one by wpcom_user_id, try by the email?
624
		if ( empty( $user ) && self::match_by_email() ) {
625
			$user = get_user_by( 'email', $user_data->email );
626
			if ( $user ) {
627
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
628
			}
629
		}
630
631
		// If we've still got nothing, create the user.
632
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
633
			// If not matching by email we still need to verify the email does not exist
634
			// or this blows up
635
			/**
636
			 * If match_by_email is true, we know the email doesn't exist, as it would have
637
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
638
			 * user, then we know that email is unused, so it's safe to add.
639
			 */
640
			if ( self::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
641
				$username = $user_data->login;
642
643
				if ( username_exists( $username ) ) {
644
					$username = $user_data->login . '_' . $user_data->ID;
645
				}
646
647
				$tries = 0;
648
				while ( username_exists( $username ) ) {
649
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
650
					if ( $tries++ >= 5 ) {
651
						wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
652
					}
653
				}
654
655
				$password = wp_generate_password( 20 );
656
				$user_id  = wp_create_user( $username, $password, $user_data->email );
657
				$user     = get_userdata( $user_id );
658
659
				$user->display_name = $user_data->display_name;
660
				$user->first_name   = $user_data->first_name;
661
				$user->last_name    = $user_data->last_name;
662
				$user->url          = $user_data->url;
663
				$user->description  = $user_data->description;
664
				wp_update_user( $user );
665
666
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
667
			} else {
668
				$this->user_data = $user_data;
669
				// do_action( 'wp_login_failed', $user_data->login );
670
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
671
				return;
672
			}
673
		}
674
675
		/**
676
		 * Fires after we got login information from WordPress.com.
677
		 *
678
		 * @module sso
679
		 *
680
		 * @since 2.6.0
681
		 *
682
		 * @param array $user WordPress.com User information.
683
		 * @param object $user_data User Login information.
684
		 */
685
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
686
687
		if ( $user ) {
688
			// Cache the user's details, so we can present it back to them on their user screen.
689
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
690
691
			$remember = false;
692 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
693
				$remember = true;
694
				// And then purge it
695
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
696
			}
697
			/**
698
			 * Filter the remember me value.
699
			 *
700
			 * @module sso
701
			 *
702
			 * @since 2.8.0
703
			 *
704
			 * @param bool $remember Is the remember me option checked?
705
			 */
706
			$remember = apply_filters( 'jetpack_remember_login', $remember );
707
			wp_set_auth_cookie( $user->ID, $remember );
708
709
			/** This filter is documented in core/src/wp-includes/user.php */
710
			do_action( 'wp_login', $user->user_login, $user );
711
712
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
713
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
714
715
			// If we have a saved redirect to request in a cookie
716 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
717
				// Set that as the requested redirect to
718
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
719
				// And then purge it
720
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
721
			}
722
723
			wp_safe_redirect(
724
				/** This filter is documented in core/src/wp-login.php */
725
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
726
			);
727
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method handle_login() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
728
		}
729
730
		$this->user_data = $user_data;
731
		/** This filter is documented in core/src/wp-includes/pluggable.php */
732
		do_action( 'wp_login_failed', $user_data->login );
733
		add_action( 'login_message', array( $this, 'cant_find_user' ) );
734
	}
735
736
	static function profile_page_url() {
737
		return admin_url( 'profile.php' );
738
	}
739
740
	static function match_by_email() {
741
		$match_by_email = ( 1 == get_option( 'jetpack_sso_match_by_email', true ) ) ? true: false;
742
		$match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : $match_by_email;
743
744
		/**
745
		 * Link the local account to an account on WordPress.com using the same email address.
746
		 *
747
		 * @module sso
748
		 *
749
		 * @since 2.6.0
750
		 *
751
		 * @param bool $match_by_email Should we link the local account to an account on WordPress.com using the same email address. Default to false.
752
		 */
753
		return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
754
	}
755
756
	static function new_user_override() {
757
		$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
758
759
		/**
760
		 * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
761
		 *
762
		 * @module sso
763
		 *
764
		 * @since 2.6.0
765
		 *
766
		 * @param bool $new_user_override Allow users to register on your site with a WordPress.com account. Default to false.
767
		 */
768
		return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
769
	}
770
771
	function allowed_redirect_hosts( $hosts ) {
772
		if ( empty( $hosts ) ) {
773
			$hosts = array();
774
		}
775
		$hosts[] = 'wordpress.com';
776
777
		return array_unique( $hosts );
778
	}
779
780
	function button( $args = array() ) {
781
		$defaults = array(
782
			'action'  => 'jetpack-sso',
783
		);
784
785
		$args = wp_parse_args( $args, $defaults );
786
787
		if ( ! empty( $_GET['redirect_to'] ) ) {
788
			$args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
789
		}
790
791
		$url  = add_query_arg( $args, wp_login_url() );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
792
793
		$css = "<style>
794
		.jetpack-sso.button {
795
			position: relative;
796
			padding-left: 37px;
797
		}
798
		.jetpack-sso.button:before {
799
			display: block;
800
			box-sizing: border-box;
801
			padding: 7px 0 0;
802
			text-align: center;
803
			position: absolute;
804
			top: -1px;
805
			left: -1px;
806
			border-radius: 2px 0 0 2px;
807
			content: '\\f205';
808
			background: #0074a2;
809
			color: #fff;
810
			-webkit-font-smoothing: antialiased;
811
			width: 30px;
812
			height: 107%;
813
			height: calc( 100% + 2px );
814
			font: normal 22px/1 Genericons !important;
815
			text-shadow: none;
816
		}
817
		@media screen and (min-width: 783px) {
818
			.jetpack-sso.button:before {
819
				padding-top: 3px;
820
			}
821
		}
822
		.jetpack-sso.button:hover {
823
			border: 1px solid #aaa;
824
		}";
825
826
		if ( version_compare( $GLOBALS['wp_version'], '3.8-alpha', '<' ) ) {
827
			$css .= "
828
			.jetpack-sso.button:before {
829
				width: 25px;
830
				font-size: 18px !important;
831
			}
832
			";
833
		}
834
835
		$css .= "</style>";
836
837
		$button = sprintf( '<a href="%1$s" class="jetpack-sso button">%2$s</a>', esc_url( $url ), esc_html__( 'Log in with WordPress.com', 'jetpack' ) );
838
		return $button . $css;
839
	}
840
841
	function build_sso_url( $args = array() ) {
842
		$defaults = array(
843
			'action'    => 'jetpack-sso',
844
			'site_id'   => Jetpack_Options::get_option( 'id' ),
845
			'sso_nonce' => self::request_initial_nonce(),
846
		);
847
848
		if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
849
			$defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
850
		}
851
852
		$args = wp_parse_args( $args, $defaults );
853
		$url  = add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
854
855
		return $url;
856
	}
857
858
	/**
859
 	* Determines local user associated with a given WordPress.com user ID.
860
 	*
861
 	* @since 2.6.0
862
 	*
863
 	* @param int $wpcom_user_id User ID from WordPress.com
864
 	* @return object Local user object if found, null if not.
865
	*/
866
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
867
		$user_query = new WP_User_Query( array(
868
			'meta_key'   => 'wpcom_user_id',
869
			'meta_value' => intval( $wpcom_user_id ),
870
			'number'     => 1,
871
		) );
872
873
		$users = $user_query->get_results();
874
		return $users ? array_shift( $users ) : null;
875
	}
876
877
	/**
878
	 * Error message displayed on the login form when two step is required and
879
	 * the user's account on WordPress.com does not have two step enabled.
880
	 *
881
	 * @since 2.7
882
	 * @param string $message
883
	 * @return string
884
	 **/
885
	public function error_msg_enable_two_step( $message ) {
886
		$err = __( sprintf( 'This site requires two step authentication be enabled for your user account on WordPress.com. Please visit the <a href="%1$s"> Security Settings</a> page to enable two step', 'https://wordpress.com/me/security/two-step' ) , 'jetpack' );
887
888
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
889
890
		return $message;
891
	}
892
893
	/**
894
	 * Error message displayed when the user tries to SSO, but match by email
895
	 * is off and they already have an account with their email address on
896
	 * this site.
897
	 *
898
	 * @param string $message
899
	 * @return string
900
	 */
901
	public function error_msg_email_already_exists( $message ) {
902
		$err = __( sprintf( 'You already have an account on this site. Please visit your <a href="%1$s">profile page</a> page to link your account to WordPress.com!', admin_url( 'profile.php' ) ) , 'jetpack' );
903
904
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
905
906
		return $message;
907
	}
908
909
	/**
910
	 * Message displayed when the site admin has disabled the default WordPress
911
	 * login form in Settings > General > Single Sign On
912
	 *
913
	 * @since 2.7
914
	 * @param string $message
915
	 * @return string
916
	 **/
917
	public function msg_login_by_jetpack( $message ) {
918
919
		$msg = __( sprintf( 'Jetpack authenticates through WordPress.com — to log in, enter your WordPress.com username and password, or <a href="%1$s">visit WordPress.com</a> to create a free account now.', 'http://wordpress.com/signup' ) , 'jetpack' );
920
921
		/**
922
		 * Filter the message displayed when the default WordPress login form is disabled.
923
		 *
924
		 * @module sso
925
		 *
926
		 * @since 2.8.0
927
		 *
928
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
929
		 */
930
		$msg = apply_filters( 'jetpack_sso_disclaimer_message', $msg );
931
932
		$message .= sprintf( '<p class="message">%s</p>', $msg );
933
		return $message;
934
	}
935
936
	/**
937
	 * Error message displayed on the login form when the user attempts
938
	 * to post to the login form and it is disabled.
939
	 *
940
	 * @since 2.8
941
	 * @param string $message
942
	 * @param string
943
	 **/
944
	public function error_msg_login_method_not_allowed( $message ) {
945
		$err = __( 'Login method not allowed' , 'jetpack' );
946
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
947
948
		return $message;
949
	}
950
	function cant_find_user( $message ) {
951
		if ( self::match_by_email() ) {
952
			$err_format = __( 'We couldn\'t find an account with the email <strong><code>%1$s</code></strong> to log you in with.  If you already have an account on <strong>%2$s</strong>, please make sure that <strong><code>%1$s</code></strong> is configured as the email address, or that you have connected to WordPress.com on your profile page.', 'jetpack' );
953
		} else {
954
			$err_format = __( 'We couldn\'t find any account on <strong>%2$s</strong> that is linked to your WordPress.com account to log you in with.  If you already have an account on <strong>%2$s</strong>, please make sure that you have connected to WordPress.com on your profile page.', 'jetpack' );
955
		}
956
		$err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
957
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
958
		return $message;
959
	}
960
961
	/**
962
	 * Deal with user connections...
963
	 */
964
	function admin_init() {
965
		add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
966
		add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
967
968
		if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
969
			$user = wp_get_current_user();
970
			// Remove the connection on the wpcom end.
971
			self::delete_connection_for_user( $user->ID );
972
			// Clear it locally.
973
			delete_user_meta( $user->ID, 'wpcom_user_id' );
974
			delete_user_meta( $user->ID, 'wpcom_user_data' );
975
			// Forward back to the profile page.
976
			wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
977
		}
978
	}
979
980
	/**
981
	 * Determines if a local user is connected to WordPress.com
982
	 *
983
	 * @since 2.8
984
	 * @param integer $user_id - Local user id
985
	 * @return boolean
986
	 **/
987
	public function is_user_connected( $user_id ) {
988
		return $this->get_user_data( $user_id ) ;
989
	}
990
991
	/**
992
	 * Retrieves a user's WordPress.com data
993
	 *
994
	 * @since 2.8
995
	 * @param integer $user_id - Local user id
996
	 * @return mixed null or stdClass
997
	 **/
998
	public function get_user_data( $user_id ) {
999
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1000
	}
1001
1002
	function edit_profile_fields( $user ) {
1003
		wp_enqueue_style( 'genericons' );
1004
		?>
1005
1006
		<h3 id="single-sign-on"><?php _e( 'Single Sign On', 'jetpack' ); ?></h3>
1007
		<p><?php _e( 'Connecting with Single Sign On enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
1008
1009
		<?php if ( $this->is_user_connected( $user->ID ) ) : /* If the user is currently connected... */ ?>
1010
			<?php $user_data = $this->get_user_data( $user->ID ); ?>
1011
			<table class="form-table jetpack-sso-form-table">
1012
				<tbody>
1013
					<tr>
1014
						<td>
1015
							<div class="profile-card">
1016
								<?php echo get_avatar( $user_data->email ); ?>
1017
								<p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
1018
								<p><?php echo esc_html( $user_data->login ); ?></p>
1019
								<span class="two_step">
1020
									<?php
1021
										if( $user_data->two_step_enabled ) {
1022
											?> <p class="enabled"><a href="https://wordpress.com/me/security/two-step"><?php _e( 'Two-Step Authentication Enabled', 'jetpack' ); ?></a></p> <?php
1023
										} else {
1024
											?> <p class="disabled"><a href="https://wordpress.com/me/security/two-step"><?php _e( 'Two-Step Authentication Disabled', 'jetpack' ); ?></a></p> <?php
1025
										}
1026
									?>
1027
								</span>
1028
1029
							</div>
1030
							<p><a class="button button-secondary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'jetpack_sso', 'purge' ), 'jetpack_sso_purge' ) ); ?>"><?php _e( 'Unlink This Account', 'jetpack' ); ?></a></p>
1031
						</td>
1032
					</tr>
1033
				</tbody>
1034
			</table>
1035
1036
			<style>
1037
			.jetpack-sso-form-table td {
1038
				padding-left: 0;
1039
			}
1040
1041
			.jetpack-sso-form-table .profile-card {
1042
				padding: 10px;
1043
				background: #fff;
1044
				overflow: hidden;
1045
				max-width: 400px;
1046
				box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.1 );
1047
				margin-bottom: 1em;
1048
			}
1049
1050
			.jetpack-sso-form-table .profile-card img {
1051
				float: left;
1052
				margin-right: 1em;
1053
				width: 48px;
1054
				height: 48px;
1055
			}
1056
1057
			.jetpack-sso-form-table .profile-card .connected {
1058
				float: right;
1059
				margin-right: 0.5em;
1060
				color: #0a0;
1061
			}
1062
1063
			.jetpack-sso-form-table .profile-card p {
1064
				margin-top: 0.7em;
1065
				font-size: 1.2em;
1066
			}
1067
1068
			.jetpack-sso-form-table .profile-card .two_step .enabled a {
1069
				float: right;
1070
				color: #0a0;
1071
			}
1072
1073
			.jetpack-sso-form-table .profile-card .two_step .disabled a {
1074
				float: right;
1075
				color: red;
1076
			}
1077
			</style>
1078
1079
		<?php elseif ( get_current_user_id() == $user->ID && Jetpack::is_user_connected( $user->ID ) ) : ?>
1080
1081
			<?php echo $this->button( 'state=sso-link-user&_wpnonce=' . wp_create_nonce('sso-link-user') ); // update ?>
0 ignored issues
show
Documentation introduced by
'state=sso-link-user&_wp..._nonce('sso-link-user') is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1082
1083
		<?php else : ?>
1084
1085
			<p><?php esc_html_e( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?></p>
1086
			<a href="<?php echo Jetpack::init()->build_connect_url( false, get_edit_profile_url( get_current_user_id() ) . '#single-sign-on' ); ?>" class="button button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a>
0 ignored issues
show
Documentation introduced by
get_edit_profile_url(get...()) . '#single-sign-on' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1087
1088
		<?php endif;
1089
	}
1090
}
1091
1092
Jetpack_SSO::get_instance();
1093