Completed
Push — renovate/mocha-8.x ( 07030e...e8e64c )
by
unknown
28:17 queued 19:09
created

Jetpack_SSO::inject_sso_jitm()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 2
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Roles;
4
use Automattic\Jetpack\Status;
5
use Automattic\Jetpack\Tracking;
6
use Automattic\Jetpack\Redirect;
7
8
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' );
9
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-notices.php' );
10
11
/**
12
 * Module Name: Secure Sign On
13
 * Module Description: Allow users to log in to this site using WordPress.com accounts
14
 * Sort Order: 30
15
 * Recommendation Order: 5
16
 * First Introduced: 2.6
17
 * Requires Connection: Yes
18
 * Auto Activate: No
19
 * Module Tags: Developers
20
 * Feature: Security
21
 * Additional Search Queries: sso, single sign on, login, log in, 2fa, two-factor
22
 */
23
24
class Jetpack_SSO {
25
	static $instance = null;
26
27
	private function __construct() {
28
29
		self::$instance = $this;
30
31
		add_action( 'admin_init',                      array( $this, 'maybe_authorize_user_after_sso' ), 1 );
32
		add_action( 'admin_init',                      array( $this, 'register_settings' ) );
33
		add_action( 'login_init',                      array( $this, 'login_init' ) );
34
		add_action( 'delete_user',                     array( $this, 'delete_connection_for_user' ) );
35
		add_filter( 'jetpack_xmlrpc_methods',          array( $this, 'xmlrpc_methods' ) );
36
		add_action( 'init',                            array( $this, 'maybe_logout_user' ), 5 );
37
		add_action( 'jetpack_modules_loaded',          array( $this, 'module_configure_button' ) );
38
		add_action( 'login_form_logout',               array( $this, 'store_wpcom_profile_cookies_on_logout' ) );
39
		add_action( 'jetpack_unlinked_user',           array( $this, 'delete_connection_for_user') );
40
		add_action( 'wp_login',                        array( 'Jetpack_SSO', 'clear_cookies_after_login' ) );
41
42
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
43
		add_action( 'login_form_jetpack-sso', '__return_true' );
44
	}
45
46
	/**
47
	 * Returns the single instance of the Jetpack_SSO object
48
	 *
49
	 * @since 2.8
50
	 * @return Jetpack_SSO
51
	 **/
52
	public static function get_instance() {
53
		if ( ! is_null( self::$instance ) ) {
54
			return self::$instance;
55
		}
56
57
		return self::$instance = new Jetpack_SSO;
58
	}
59
60
	/**
61
	 * Add configure button and functionality to the module card on the Jetpack screen
62
	 **/
63
	public static function module_configure_button() {
64
		Jetpack::enable_module_configurable( __FILE__ );
65
	}
66
67
	/**
68
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
69
	 * to logout and reauthenticate with the site.
70
	 **/
71
	public function maybe_logout_user() {
72
		global $current_user;
73
74
		if ( 1 == $current_user->jetpack_force_logout ) {
75
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
76
			self::delete_connection_for_user( $current_user->ID );
77
			wp_logout();
78
			wp_safe_redirect( wp_login_url() );
79
			exit;
80
		}
81
	}
82
83
	/**
84
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
85
	 *
86
	 * @param array $methods
87
	 * @return array
88
	 **/
89
	public function xmlrpc_methods( $methods ) {
90
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
91
		return $methods;
92
	}
93
94
	/**
95
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
96
	 * the next time the user visits the site.
97
	 **/
98
	public function xmlrpc_user_disconnect( $user_id ) {
99
		$user_query = new WP_User_Query(
100
			array(
101
				'meta_key' => 'wpcom_user_id',
102
				'meta_value' => $user_id,
103
			)
104
		);
105
		$user = $user_query->get_results();
106
		$user = $user[0];
107
108
		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...
109
			$user = wp_set_current_user( $user->ID );
110
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
111
			self::delete_connection_for_user( $user->ID );
112
			return true;
113
		}
114
		return false;
115
	}
116
117
	/**
118
	 * Enqueues scripts and styles necessary for SSO login.
119
	 */
120
	public function login_enqueue_scripts() {
121
		global $action;
122
123
		if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
124
			return;
125
		}
126
127
		if ( is_rtl() ) {
128
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login-rtl.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
129
		} else {
130
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
131
		}
132
133
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
134
	}
135
136
	/**
137
	 * Adds Jetpack SSO classes to login body
138
	 *
139
	 * @param  array $classes Array of classes to add to body tag
140
	 * @return array          Array of classes to add to body tag
141
	 */
142
	public function login_body_class( $classes ) {
143
		global $action;
144
145
		if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
146
			return $classes;
147
		}
148
149
		// Always add the jetpack-sso class so that we can add SSO specific styling even when the SSO form isn't being displayed.
150
		$classes[] = 'jetpack-sso';
151
152
		if ( ! ( new Status() )->is_staging_site() ) {
153
			/**
154
			 * Should we show the SSO login form?
155
			 *
156
			 * $_GET['jetpack-sso-default-form'] is used to provide a fallback in case JavaScript is not enabled.
157
			 *
158
			 * The default_to_sso_login() method allows us to dynamically decide whether we show the SSO login form or not.
159
			 * The SSO module uses the method to display the default login form if we can not find a user to log in via SSO.
160
			 * But, the method could be filtered by a site admin to always show the default login form if that is preferred.
161
			 */
162
			if ( empty( $_GET['jetpack-sso-show-default-form'] ) && Jetpack_SSO_Helpers::show_sso_login() ) {
163
				$classes[] = 'jetpack-sso-form-display';
164
			}
165
		}
166
167
		return $classes;
168
	}
169
170
	public function print_inline_admin_css() {
171
		?>
172
			<style>
173
				.jetpack-sso .message {
174
					margin-top: 20px;
175
				}
176
177
				.jetpack-sso #login .message:first-child,
178
				.jetpack-sso #login h1 + .message {
179
					margin-top: 0;
180
				}
181
			</style>
182
		<?php
183
	}
184
185
	/**
186
	 * Adds settings fields to Settings > General > Secure Sign On that allows users to
187
	 * turn off the login form on wp-login.php
188
	 *
189
	 * @since 2.7
190
	 **/
191
	public function register_settings() {
192
193
		add_settings_section(
194
			'jetpack_sso_settings',
195
			__( 'Secure Sign On' , 'jetpack' ),
196
			'__return_false',
197
			'jetpack-sso'
198
		);
199
200
		/*
201
		 * Settings > General > Secure Sign On
202
		 * Require two step authentication
203
		 */
204
		register_setting(
205
			'jetpack-sso',
206
			'jetpack_sso_require_two_step',
207
			array( $this, 'validate_jetpack_sso_require_two_step' )
208
		);
209
210
		add_settings_field(
211
			'jetpack_sso_require_two_step',
212
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
213
			array( $this, 'render_require_two_step' ),
214
			'jetpack-sso',
215
			'jetpack_sso_settings'
216
		);
217
218
		/*
219
		 * Settings > General > Secure Sign On
220
		 */
221
		register_setting(
222
			'jetpack-sso',
223
			'jetpack_sso_match_by_email',
224
			array( $this, 'validate_jetpack_sso_match_by_email' )
225
		);
226
227
		add_settings_field(
228
			'jetpack_sso_match_by_email',
229
			'', // __( 'Match by Email' , 'jetpack' ),
230
			array( $this, 'render_match_by_email' ),
231
			'jetpack-sso',
232
			'jetpack_sso_settings'
233
		);
234
	}
235
236
	/**
237
	 * Builds the display for the checkbox allowing user to require two step
238
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
239
	 *
240
	 * @since 2.7
241
	 **/
242
	public function render_require_two_step() {
243
		?>
244
		<label>
245
			<input
246
				type="checkbox"
247
				name="jetpack_sso_require_two_step"
248
				<?php checked( Jetpack_SSO_Helpers::is_two_step_required() ); ?>
249
				<?php disabled( Jetpack_SSO_Helpers::is_require_two_step_checkbox_disabled() ); ?>
250
			>
251
			<?php esc_html_e( 'Require Two-Step Authentication' , 'jetpack' ); ?>
252
		</label>
253
		<?php
254
	}
255
256
	/**
257
	 * Validate the require  two step checkbox in Settings > General
258
	 *
259
	 * @since 2.7
260
	 * @return boolean
261
	 **/
262
	public function validate_jetpack_sso_require_two_step( $input ) {
263
		return ( ! empty( $input ) ) ? 1 : 0;
264
	}
265
266
	/**
267
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
268
	 * Displays in Settings > General
269
	 *
270
	 * @since 2.9
271
	 **/
272
	public function render_match_by_email() {
273
		?>
274
			<label>
275
				<input
276
					type="checkbox"
277
					name="jetpack_sso_match_by_email"
278
					<?php checked( Jetpack_SSO_Helpers::match_by_email() ); ?>
279
					<?php disabled( Jetpack_SSO_Helpers::is_match_by_email_checkbox_disabled() ); ?>
280
				>
281
				<?php esc_html_e( 'Match by Email', 'jetpack' ); ?>
282
			</label>
283
		<?php
284
	}
285
286
	/**
287
	 * Validate the match by email check in Settings > General
288
	 *
289
	 * @since 2.9
290
	 * @return boolean
291
	 **/
292
	public function validate_jetpack_sso_match_by_email( $input ) {
293
		return ( ! empty( $input ) ) ? 1 : 0;
294
	}
295
296
	/**
297
	 * Checks to determine if the user wants to login on wp-login
298
	 *
299
	 * This function mostly exists to cover the exceptions to login
300
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
301
	 * does not have to exist. By default WordPress assumes login if an action
302
	 * is not set, however this may not be true, as in the case of logout
303
	 * where $_GET[loggedout] is instead set
304
	 *
305
	 * @return boolean
306
	 **/
307
	private function wants_to_login() {
308
		$wants_to_login = false;
309
310
		// Cover default WordPress behavior
311
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
312
313
		// And now the exceptions
314
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
315
316
		if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
317
			$wants_to_login = true;
318
		}
319
320
		return $wants_to_login;
321
	}
322
323
	function login_init() {
324
		global $action;
325
326
		$tracking = new Tracking();
327
328
		if ( Jetpack_SSO_Helpers::should_hide_login_form() ) {
329
			/**
330
			 * Since the default authenticate filters fire at priority 20 for checking username and password,
331
			 * let's fire at priority 30. wp_authenticate_spam_check is fired at priority 99, but since we return a
332
			 * WP_Error in disable_default_login_form, then we won't trigger spam processing logic.
333
			 */
334
			add_filter( 'authenticate', array( 'Jetpack_SSO_Notices', 'disable_default_login_form' ), 30 );
335
336
			/**
337
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
338
			 *
339
			 * @module sso
340
			 *
341
			 * @since 2.8.0
342
			 *
343
			 * @param bool true Should the disclaimer be displayed. Default to true.
344
			 */
345
			$display_sso_disclaimer = apply_filters( 'jetpack_sso_display_disclaimer', true );
346
			if ( $display_sso_disclaimer ) {
347
				add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'msg_login_by_jetpack' ) );
348
			}
349
		}
350
351
		 if ( 'jetpack-sso' === $action ) {
352
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
353
				$this->handle_login();
354
				$this->display_sso_login_form();
355
			} else {
356
				if ( ( new Status() )->is_staging_site() ) {
357
					add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) );
358 View Code Duplication
				} else {
359
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
360
					add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
361
					$reauth = ! empty( $_GET['force_reauth'] );
362
					$sso_url = $this->get_sso_url_or_die( $reauth );
363
364
					$tracking->record_user_event( 'sso_login_redirect_success' );
365
					wp_safe_redirect( $sso_url );
366
					exit;
367
				}
368
			}
369
		} else if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
370
371
			// Save cookies so we can handle redirects after SSO
372
			$this->save_cookies();
373
374
			/**
375
			 * Check to see if the site admin wants to automagically forward the user
376
			 * to the WordPress.com login page AND  that the request to wp-login.php
377
			 * is not something other than login (Like logout!)
378
			 */
379 View Code Duplication
			if ( Jetpack_SSO_Helpers::bypass_login_forward_wpcom() && $this->wants_to_login() ) {
380
				add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
381
				$reauth = ! empty( $_GET['force_reauth'] );
382
				$sso_url = $this->get_sso_url_or_die( $reauth );
383
				$tracking->record_user_event( 'sso_login_redirect_bypass_success' );
384
				wp_safe_redirect( $sso_url );
385
				exit;
386
			}
387
388
			$this->display_sso_login_form();
389
		}
390
	}
391
392
	/**
393
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
394
	 * up the hooks required to display the SSO form.
395
	 */
396
	public function display_sso_login_form() {
397
		add_filter( 'login_body_class', array( $this, 'login_body_class' ) );
398
		add_action( 'login_head',       array( $this, 'print_inline_admin_css' ) );
399
400
		if ( ( new Status() )->is_staging_site() ) {
401
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) );
402
			return;
403
		}
404
405
		$sso_nonce = self::request_initial_nonce();
406
		if ( is_wp_error( $sso_nonce ) ) {
407
			return;
408
		}
409
410
		add_action( 'login_form',            array( $this, 'login_form' ) );
411
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
412
	}
413
414
	/**
415
	 * Conditionally save the redirect_to url as a cookie.
416
	 *
417
	 * @since 4.6.0 Renamed to save_cookies from maybe_save_redirect_cookies
418
	 */
419
	public static function save_cookies() {
420
		if ( headers_sent() ) {
421
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'headers_sent'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
422
		}
423
424
		setcookie(
425
			'jetpack_sso_original_request',
426
			esc_url_raw( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ),
427
			time() + HOUR_IN_SECONDS,
428
			COOKIEPATH,
429
			COOKIE_DOMAIN,
430
			is_ssl(),
431
			true
432
		);
433
434
		if ( ! empty( $_GET['redirect_to'] ) ) {
435
			// If we have something to redirect to
436
			$url = esc_url_raw( $_GET['redirect_to'] );
437
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
438
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
439
			// Otherwise, if it's already set, purge it.
440
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
441
		}
442
	}
443
444
	/**
445
	 * Outputs the Jetpack SSO button and description as well as the toggle link
446
	 * for switching between Jetpack SSO and default login.
447
	 */
448
	function login_form() {
449
		$site_name = get_bloginfo( 'name' );
450
		if ( ! $site_name ) {
451
			$site_name = get_bloginfo( 'url' );
452
		}
453
454
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] )
455
			? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ]
456
			: false;
457
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] )
458
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ]
459
			: false;
460
461
		?>
462
		<div id="jetpack-sso-wrap">
463
			<?php
464
				/**
465
				 * Allow extension above Jetpack's SSO form.
466
				 *
467
				 * @module sso
468
				 *
469
				 * @since 8.6.0
470
				 */
471
				do_action( 'jetpack_sso_login_form_above_wpcom' );
472
473
				if ( $display_name && $gravatar ) : ?>
474
				<div id="jetpack-sso-wrap__user">
475
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
476
477
					<h2>
478
						<?php
479
							echo wp_kses(
480
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
481
								array( 'span' => true )
482
							);
483
						?>
484
					</h2>
485
				</div>
486
487
			<?php endif; ?>
488
489
490
			<div id="jetpack-sso-wrap__action">
491
				<?php echo $this->build_sso_button( array(), 'is_primary' ); ?>
0 ignored issues
show
Documentation introduced by
'is_primary' 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...
492
493
				<?php if ( $display_name && $gravatar ) : ?>
494
					<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
495
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
496
					</a>
497
				<?php else : ?>
498
					<p>
499
						<?php
500
							echo esc_html(
501
								sprintf(
502
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
503
									esc_html( $site_name )
504
								)
505
							);
506
						?>
507
					</p>
508
				<?php endif; ?>
509
			</div>
510
511
			<?php
512
				/**
513
				 * Allow extension below Jetpack's SSO form.
514
				 *
515
				 * @module sso
516
				 *
517
				 * @since 8.6.0
518
				 */
519
				do_action( 'jetpack_sso_login_form_below_wpcom' );
520
521
				if ( ! Jetpack_SSO_Helpers::should_hide_login_form() ) : ?>
522
					<div class="jetpack-sso-or">
523
						<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
524
					</div>
525
526
					<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '1' ) ); ?>" class="jetpack-sso-toggle wpcom">
527
						<?php
528
							esc_html_e( 'Log in with username and password', 'jetpack' )
529
						?>
530
					</a>
531
532
					<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '0' ) ); ?>" class="jetpack-sso-toggle default">
533
						<?php
534
							esc_html_e( 'Log in with WordPress.com', 'jetpack' )
535
						?>
536
					</a>
537
			<?php endif; ?>
538
		</div>
539
		<?php
540
	}
541
542
	/**
543
	 * Clear the cookies that store the profile information for the last
544
	 * WPCOM user to connect.
545
	 */
546
	static function clear_wpcom_profile_cookies() {
547 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
548
			setcookie(
549
				'jetpack_sso_wpcom_name_' . COOKIEHASH,
550
				' ',
551
				time() - YEAR_IN_SECONDS,
552
				COOKIEPATH,
553
				COOKIE_DOMAIN,
554
				is_ssl()
555
			);
556
		}
557
558 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
559
			setcookie(
560
				'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
561
				' ',
562
				time() - YEAR_IN_SECONDS,
563
				COOKIEPATH,
564
				COOKIE_DOMAIN,
565
				is_ssl()
566
			);
567
		}
568
	}
569
570
	/**
571
	 * Clear cookies that are no longer needed once the user has logged in.
572
	 *
573
	 * @since 4.8.0
574
	 */
575
	static function clear_cookies_after_login() {
576
		self::clear_wpcom_profile_cookies();
577 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_nonce' ] ) ) {
578
			setcookie(
579
				'jetpack_sso_nonce',
580
				' ',
581
				time() - YEAR_IN_SECONDS,
582
				COOKIEPATH,
583
				COOKIE_DOMAIN,
584
				is_ssl()
585
			);
586
		}
587
588 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_original_request' ] ) ) {
589
			setcookie(
590
				'jetpack_sso_original_request',
591
				' ',
592
				time() - YEAR_IN_SECONDS,
593
				COOKIEPATH,
594
				COOKIE_DOMAIN,
595
				is_ssl()
596
			);
597
		}
598
599 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_redirect_to' ] ) ) {
600
			setcookie(
601
				'jetpack_sso_redirect_to',
602
				' ',
603
				time() - YEAR_IN_SECONDS,
604
				COOKIEPATH,
605
				COOKIE_DOMAIN,
606
				is_ssl()
607
			);
608
		}
609
	}
610
611
	static function delete_connection_for_user( $user_id ) {
612
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
613
			return;
614
		}
615
616
		$xml = new Jetpack_IXR_Client( array(
617
			'wpcom_user_id' => $user_id,
618
		) );
619
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
620
621
		if ( $xml->isError() ) {
622
			return false;
623
		}
624
625
		// Clean up local data stored for SSO
626
		delete_user_meta( $user_id, 'wpcom_user_id' );
627
		delete_user_meta( $user_id, 'wpcom_user_data'  );
628
		self::clear_wpcom_profile_cookies();
629
630
		return $xml->getResponse();
631
	}
632
633
	static function request_initial_nonce() {
634
		$nonce = ! empty( $_COOKIE[ 'jetpack_sso_nonce' ] )
635
			? $_COOKIE[ 'jetpack_sso_nonce' ]
636
			: false;
637
638
		if ( ! $nonce ) {
639
			$xml = new Jetpack_IXR_Client();
640
			$xml->query( 'jetpack.sso.requestNonce' );
641
642
			if ( $xml->isError() ) {
643
				return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $xml->getErrorCode().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
644
			}
645
646
			$nonce = $xml->getResponse();
647
648
			setcookie(
649
				'jetpack_sso_nonce',
650
				$nonce,
651
				time() + ( 10 * MINUTE_IN_SECONDS ),
652
				COOKIEPATH,
653
				COOKIE_DOMAIN,
654
				is_ssl()
655
			);
656
		}
657
658
		return sanitize_key( $nonce );
659
	}
660
661
	/**
662
	 * The function that actually handles the login!
663
	 */
664
	function handle_login() {
665
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
666
		$wpcom_user_id = (int) $_GET['user_id'];
667
668
		$xml = new Jetpack_IXR_Client();
669
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
670
671
		$user_data = $xml->isError() ? false : $xml->getResponse();
672
		if ( empty( $user_data ) ) {
673
			add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
674
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_invalid_response_data' ) );
675
			return;
676
		}
677
678
		$user_data = (object) $user_data;
679
		$user = null;
680
681
		/**
682
		 * Fires before Jetpack's SSO modifies the log in form.
683
		 *
684
		 * @module sso
685
		 *
686
		 * @since 2.6.0
687
		 *
688
		 * @param object $user_data WordPress.com User information.
689
		 */
690
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
691
692
		$tracking = new Tracking();
693
694
		if ( Jetpack_SSO_Helpers::is_two_step_required() && 0 === (int) $user_data->two_step_enabled ) {
695
			$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...
696
697
			$tracking->record_user_event( 'sso_login_failed', array(
698
				'error_message' => 'error_msg_enable_two_step'
699
			) );
700
701
			$error = new WP_Error( 'two_step_required', __( 'You must have Two-Step Authentication enabled on your WordPress.com account.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'two_step_required'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
702
703
			/** This filter is documented in core/src/wp-includes/pluggable.php */
704
			do_action( 'wp_login_failed', $user_data->login, $error );
705
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_enable_two_step' ) );
706
			return;
707
		}
708
709
		$user_found_with = '';
710
		if ( empty( $user ) && isset( $user_data->external_user_id ) ) {
711
			$user_found_with = 'external_user_id';
712
			$user = get_user_by( 'id', intval( $user_data->external_user_id ) );
713
			if ( $user ) {
714
				$expected_id = get_user_meta( $user->ID, 'wpcom_user_id', true );
715
				if ( $expected_id && $expected_id != $user_data->ID ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
716
					$error = new WP_Error( 'expected_wpcom_user', __( 'Something got a little mixed up and an unexpected WordPress.com user logged in.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'expected_wpcom_user'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
717
718
					/** This filter is documented in core/src/wp-includes/pluggable.php */
719
					do_action( 'wp_login_failed', $user_data->login, $error );
720
					add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_invalid_response_data' ) ); // @todo Need to have a better notice. This is only for the sake of testing the validation.
721
					return;
722
				}
723
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
724
			}
725
		}
726
727
		// If we don't have one by wpcom_user_id, try by the email?
728
		if ( empty( $user ) && Jetpack_SSO_Helpers::match_by_email() ) {
729
			$user_found_with = 'match_by_email';
730
			$user = get_user_by( 'email', $user_data->email );
731
			if ( $user ) {
732
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
733
			}
734
		}
735
736
		// If we've still got nothing, create the user.
737
		$new_user_override_role = false;
738
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || ( $new_user_override_role = Jetpack_SSO_Helpers::new_user_override( $user_data ) ) ) ) {
739
			/**
740
			 * If not matching by email we still need to verify the email does not exist
741
			 * or this blows up
742
			 *
743
			 * If match_by_email is true, we know the email doesn't exist, as it would have
744
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
745
			 * user, then we know that email is unused, so it's safe to add.
746
			 */
747
			if ( Jetpack_SSO_Helpers::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
748
749
				if ( $new_user_override_role ) {
750
					$user_data->role = $new_user_override_role;
751
				}
752
753
				$user = Jetpack_SSO_Helpers::generate_user( $user_data );
754
				if ( ! $user ) {
755
					$tracking->record_user_event( 'sso_login_failed', array(
756
						'error_message' => 'could_not_create_username'
757
					) );
758
					add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_unable_to_create_user' ) );
759
					return;
760
				}
761
762
				$user_found_with = $new_user_override_role
763
					? 'user_created_new_user_override'
764
					: 'user_created_users_can_register';
765
			} else {
766
				$tracking->record_user_event( 'sso_login_failed', array(
767
					'error_message' => 'error_msg_email_already_exists'
768
				) );
769
770
				$this->user_data = $user_data;
771
				add_action( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_email_already_exists' ) );
772
				return;
773
			}
774
		}
775
776
		/**
777
		 * Fires after we got login information from WordPress.com.
778
		 *
779
		 * @module sso
780
		 *
781
		 * @since 2.6.0
782
		 *
783
		 * @param WP_User|false|null $user      Local User information.
784
		 * @param object             $user_data WordPress.com User Login information.
785
		 */
786
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
787
788
		if ( $user ) {
789
			// Cache the user's details, so we can present it back to them on their user screen
790
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
791
792
			add_filter( 'auth_cookie_expiration',    array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) );
793
			wp_set_auth_cookie( $user->ID, true );
794
			remove_filter( 'auth_cookie_expiration', array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) );
795
796
			/** This filter is documented in core/src/wp-includes/user.php */
797
			do_action( 'wp_login', $user->user_login, $user );
798
799
			wp_set_current_user( $user->ID );
800
801
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
802
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
803
804
			// If we have a saved redirect to request in a cookie
805
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
806
				// Set that as the requested redirect to
807
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
808
			}
809
810
			$json_api_auth_environment = Jetpack_SSO_Helpers::get_json_api_auth_environment();
811
812
			$is_json_api_auth  = ! empty( $json_api_auth_environment );
813
			$is_user_connected = Jetpack::is_user_connected( $user->ID );
814
			$roles             = new Roles();
815
			$tracking->record_user_event( 'sso_user_logged_in', array(
816
				'user_found_with'  => $user_found_with,
817
				'user_connected'   => (bool) $is_user_connected,
818
				'user_role'        => $roles->translate_current_user_to_role(),
819
				'is_json_api_auth' => (bool) $is_json_api_auth,
820
			) );
821
822
			if ( $is_json_api_auth ) {
823
				Jetpack::init()->verify_json_api_authorization_request( $json_api_auth_environment );
824
				Jetpack::init()->store_json_api_authorization_token( $user->user_login, $user );
825
826
			} else if ( ! $is_user_connected ) {
827
				wp_safe_redirect(
828
					add_query_arg(
829
						array(
830
							'redirect_to'               => $redirect_to,
831
							'request_redirect_to'       => $_request_redirect_to,
832
							'calypso_env'               => Jetpack::get_calypso_env(),
833
							'jetpack-sso-auth-redirect' => '1',
834
						),
835
						admin_url()
836
					)
837
				);
838
				exit;
839
			}
840
841
			add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
842
			wp_safe_redirect(
843
				/** This filter is documented in core/src/wp-login.php */
844
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $_request_redirect_to.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
845
			);
846
			exit;
847
		}
848
849
		add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
850
851
		$tracking->record_user_event( 'sso_login_failed', array(
852
			'error_message' => 'cant_find_user'
853
		) );
854
855
		$this->user_data = $user_data;
856
857
		$error = new WP_Error( 'account_not_found', __( 'Account not found. If you already have an account, make sure you have connected to WordPress.com.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'account_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
858
859
		/** This filter is documented in core/src/wp-includes/pluggable.php */
860
		do_action( 'wp_login_failed', $user_data->login, $error );
861
		add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'cant_find_user' ) );
862
	}
863
864
	static function profile_page_url() {
865
		return admin_url( 'profile.php' );
866
	}
867
868
	/**
869
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
870
	 *
871
	 * @param  array   $args       An array of arguments to add to the SSO URL.
872
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
873
	 * @return string              Returns the HTML markup for the button.
874
	 */
875
	function build_sso_button( $args = array(), $is_primary = false ) {
876
		$url = $this->build_sso_button_url( $args );
877
		$classes = $is_primary
878
			? 'jetpack-sso button button-primary'
879
			: 'jetpack-sso button';
880
881
		return sprintf(
882
			'<a rel="nofollow" href="%1$s" class="%2$s"><span>%3$s %4$s</span></a>',
883
			esc_url( $url ),
884
			$classes,
885
			'<span class="genericon genericon-wordpress"></span>',
886
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
887
		);
888
	}
889
890
	/**
891
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
892
	 *
893
	 * @param  array  $args An array of arguments to add to the SSO URL.
894
	 * @return string       The URL used for SSO.
895
	 */
896
	function build_sso_button_url( $args = array() ) {
897
		$defaults = array(
898
			'action'  => 'jetpack-sso',
899
		);
900
901
		$args = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,string,{"action":"string"}>, but the function expects a string.

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...
902
903
		if ( ! empty( $_GET['redirect_to'] ) ) {
904
			$args['redirect_to'] = urlencode( esc_url_raw( $_GET['redirect_to'] ) );
905
		}
906
907
		return add_query_arg( $args, wp_login_url() );
908
	}
909
910
	/**
911
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
912
	 *
913
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
914
	 * @param  array    $args    Optional query parameters.
915
	 * @return string            The WordPress.com SSO URL.
916
	 */
917
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
918
		if ( empty( $reauth ) ) {
919
			$sso_redirect = $this->build_sso_url( $args );
920
		} else {
921
			self::clear_wpcom_profile_cookies();
922
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
923
		}
924
925
		// If there was an error retrieving the SSO URL, then error.
926
		if ( is_wp_error( $sso_redirect ) ) {
927
			$error_message = sanitize_text_field(
928
				sprintf( '%s: %s', $sso_redirect->get_error_code(), $sso_redirect->get_error_message() )
0 ignored issues
show
Bug introduced by
The method get_error_code cannot be called on $sso_redirect (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
Bug introduced by
The method get_error_message cannot be called on $sso_redirect (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
929
			);
930
			$tracking = new Tracking();
931
			$tracking->record_user_event( 'sso_login_redirect_failed', array(
932
				'error_message' => $error_message
933
			) );
934
			wp_die( $error_message );
935
		}
936
937
		return $sso_redirect;
938
	}
939
940
	/**
941
	 * Build WordPress.com SSO URL with appropriate query parameters.
942
	 *
943
	 * @param array $args Optional query parameters.
944
	 * @return string WordPress.com SSO URL
945
	 */
946
	public function build_sso_url( $args = array() ) {
947
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
948
		$defaults = array(
949
			'action'       => 'jetpack-sso',
950
			'site_id'      => Jetpack_Options::get_option( 'id' ),
951
			'sso_nonce'    => $sso_nonce,
952
			'calypso_auth' => '1',
953
		);
954
955
		$args = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,*,{"action"...alypso_auth":"string"}>, but the function expects a string.

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...
956
957
		if ( is_wp_error( $args['sso_nonce'] ) ) {
958
			return $args['sso_nonce'];
959
		}
960
961
		$query = add_query_arg( $args, '' );
962
		$query = trim( $query, '?' );
963
964
		$url = Redirect::get_url(
965
			'wpcom-login',
966
			array(
967
				'query' => $query,
968
			)
969
		);
970
971
		return $url;
972
	}
973
974
	/**
975
	 * Build WordPress.com SSO URL with appropriate query parameters,
976
	 * including the parameters necessary to force the user to reauthenticate
977
	 * on WordPress.com.
978
	 *
979
	 * @param array $args Optional query parameters.
980
	 * @return string WordPress.com SSO URL
981
	 */
982
	public function build_reauth_and_sso_url( $args = array() ) {
983
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
984
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
985
986
		if ( is_wp_error( $redirect ) ) {
987
			return $redirect;
988
		}
989
990
		$defaults = array(
991
			'action'       => 'jetpack-sso',
992
			'site_id'      => Jetpack_Options::get_option( 'id' ),
993
			'sso_nonce'    => $sso_nonce,
994
			'reauth'       => '1',
995
			'redirect_to'  => urlencode( $redirect ),
996
			'calypso_auth' => '1',
997
		);
998
999
		$args = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,*,{"action"...alypso_auth":"string"}>, but the function expects a string.

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...
1000
1001
		if ( is_wp_error( $args['sso_nonce'] ) ) {
1002
			return $args['sso_nonce'];
1003
		}
1004
1005
		$query = add_query_arg( $args, '' );
1006
		$query = trim( $query, '?' );
1007
1008
		$url = Redirect::get_url(
1009
			'wpcom-login',
1010
			array(
1011
				'query' => $query,
1012
			)
1013
		);
1014
1015
		return $url;
1016
	}
1017
1018
	/**
1019
	 * Determines local user associated with a given WordPress.com user ID.
1020
	 *
1021
	 * @since 2.6.0
1022
	 *
1023
	 * @param int $wpcom_user_id User ID from WordPress.com
1024
	 * @return object Local user object if found, null if not.
1025
	 */
1026
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
1027
		$user_query = new WP_User_Query( array(
1028
			'meta_key'   => 'wpcom_user_id',
1029
			'meta_value' => intval( $wpcom_user_id ),
1030
			'number'     => 1,
1031
		) );
1032
1033
		$users = $user_query->get_results();
1034
		return $users ? array_shift( $users ) : null;
1035
	}
1036
1037
	/**
1038
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
1039
	 * WordPress.com authorization flow.
1040
	 *
1041
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1042
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1043
	 */
1044
	function maybe_authorize_user_after_sso() {
1045
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1046
			return;
1047
		}
1048
1049
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url();
1050
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to;
1051
1052
		/** This filter is documented in core/src/wp-login.php */
1053
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $request_redirect_to.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1054
1055
		/**
1056
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1057
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1058
		 */
1059
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1060
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1061
1062
		/**
1063
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1064
		 */
1065
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1066
1067
		add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
1068
		wp_safe_redirect( $connect_url );
1069
		exit;
1070
	}
1071
1072
	/**
1073
	 * Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are
1074
	 * stored when the user logs out, and then deleted when the user logs in.
1075
	 */
1076
	function store_wpcom_profile_cookies_on_logout() {
1077
		if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) {
1078
			return;
1079
		}
1080
1081
		$user_data = $this->get_user_data( get_current_user_id() );
1082
		if ( ! $user_data ) {
1083
			return;
1084
		}
1085
1086
		setcookie(
1087
			'jetpack_sso_wpcom_name_' . COOKIEHASH,
1088
			$user_data->display_name,
1089
			time() + WEEK_IN_SECONDS,
1090
			COOKIEPATH,
1091
			COOKIE_DOMAIN,
1092
			is_ssl()
1093
		);
1094
1095
		setcookie(
1096
			'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
1097
			get_avatar_url(
1098
				$user_data->email,
1099
				array( 'size' => 144, 'default' => 'mystery' )
1100
			),
1101
			time() + WEEK_IN_SECONDS,
1102
			COOKIEPATH,
1103
			COOKIE_DOMAIN,
1104
			is_ssl()
1105
		);
1106
	}
1107
1108
	/**
1109
	 * Determines if a local user is connected to WordPress.com
1110
	 *
1111
	 * @since 2.8
1112
	 * @param integer $user_id - Local user id
1113
	 * @return boolean
1114
	 **/
1115
	public function is_user_connected( $user_id ) {
1116
		return $this->get_user_data( $user_id );
1117
	}
1118
1119
	/**
1120
	 * Retrieves a user's WordPress.com data
1121
	 *
1122
	 * @since 2.8
1123
	 * @param integer $user_id - Local user id
1124
	 * @return mixed null or stdClass
1125
	 **/
1126
	public function get_user_data( $user_id ) {
1127
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1128
	}
1129
}
1130
1131
Jetpack_SSO::get_instance();
1132