Completed
Push — update/sso-audit-wp-die ( b5d2ed...15d8ab )
by
unknown
77:20 queued 62:34
created

Jetpack_SSO::request_initial_nonce()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 13
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 0
dl 13
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' );
3
4
/**
5
 * Module Name: Single Sign On
6
 * Module Description: Secure user authentication with WordPress.com.
7
 * Jumpstart Description: Lets you log in to all your Jetpack-enabled sites with one click using your WordPress.com account.
8
 * Sort Order: 30
9
 * Recommendation Order: 5
10
 * First Introduced: 2.6
11
 * Requires Connection: Yes
12
 * Auto Activate: No
13
 * Module Tags: Developers
14
 * Feature: Security, Jumpstart
15
 * Additional Search Queries: sso, single sign on, login, log in
16
 */
17
18
class Jetpack_SSO {
19
	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...
20
21
	private function __construct() {
22
23
		self::$instance = $this;
24
25
		add_action( 'admin_init',             array( $this, 'maybe_authorize_user_after_sso' ), 1 );
26
		add_action( 'admin_init',             array( $this, 'register_settings' ) );
27
		add_action( 'login_init',             array( $this, 'login_init' ) );
28
		add_action( 'delete_user',            array( $this, 'delete_connection_for_user' ) );
29
		add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
30
		add_action( 'init',                   array( $this, 'maybe_logout_user' ), 5 );
31
		add_action( 'jetpack_modules_loaded', array( $this, 'module_configure_button' ) );
32
		add_action( 'admin_enqueue_scripts',  array( $this, 'admin_enqueue_scripts' ) );
33
		add_action( 'login_form_logout',      array( $this, 'store_wpcom_profile_cookies_on_logout' ) );
34
		add_action( 'wp_login',               array( 'Jetpack_SSO', 'clear_wpcom_profile_cookies' ) );
35
		add_action( 'jetpack_unlinked_user',  array( $this, 'delete_connection_for_user') );
36
37
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
38
		add_action( 'login_form_jetpack-sso', '__return_true' );
39
	}
40
41
	/**
42
	 * Returns the single instance of the Jetpack_SSO object
43
	 *
44
	 * @since 2.8
45
	 * @return Jetpack_SSO
46
	 **/
47
	public static function get_instance() {
48
		if ( ! is_null( self::$instance ) ) {
49
			return self::$instance;
50
		}
51
52
		return self::$instance = new Jetpack_SSO;
53
	}
54
55
	/**
56
	 * Add configure button and functionality to the module card on the Jetpack screen
57
	 **/
58
	public static function module_configure_button() {
59
		Jetpack::enable_module_configurable( __FILE__ );
60
		Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
61
		Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
62
		Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
63
	}
64
65
	public static function module_configuration_load() {}
66
67
	public static function module_configuration_head() {}
68
69
	public static function module_configuration_screen() {
70
		?>
71
		<form method="post" action="options.php">
72
			<?php settings_fields( 'jetpack-sso' ); ?>
73
			<?php do_settings_sections( 'jetpack-sso' ); ?>
74
			<?php submit_button(); ?>
75
		</form>
76
		<?php
77
	}
78
79
80
	/**
81
	 * When the default login form is hidden, this method is called on the 'authenticate' filter with a priority of 30.
82
	 * This method disables the ability to submit the default login form.
83
	 *
84
	 * @param $user
85
	 *
86
	 * @return WP_Error
87
	 */
88
	public function disable_default_login_form( $user ) {
89
		if ( is_wp_error( $user ) ) {
90
			return $user;
91
		}
92
93
		/**
94
		 * Since we're returning an error that will be shown as a red notice, let's remove the
95
		 * informational "blue" notice.
96
		 */
97
		remove_filter( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
98
		return new WP_Error( 'jetpack_sso_required', $this->get_sso_required_message() );
99
	}
100
101
	/**
102
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
103
	 * to logout and reauthenticate with the site.
104
	 **/
105
	public function maybe_logout_user() {
106
		global $current_user;
107
108
		if ( 1 == $current_user->jetpack_force_logout ) {
109
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
110
			self::delete_connection_for_user( $current_user->ID );
111
			wp_logout();
112
			wp_safe_redirect( wp_login_url() );
113
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method maybe_logout_user() 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...
114
		}
115
	}
116
117
118
	/**
119
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
120
	 *
121
	 * @param array $methods
122
	 * @return array
123
	 **/
124
	public function xmlrpc_methods( $methods ) {
125
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
126
		return $methods;
127
	}
128
129
	/**
130
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
131
	 * the next time the user visits the site.
132
	 **/
133
	public function xmlrpc_user_disconnect( $user_id ) {
134
		$user_query = new WP_User_Query(
135
			array(
136
				'meta_key' => 'wpcom_user_id',
137
				'meta_value' => $user_id,
138
			)
139
		);
140
		$user = $user_query->get_results();
141
		$user = $user[0];
142
143
		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...
144
			$user = wp_set_current_user( $user->ID );
145
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
146
			self::delete_connection_for_user( $user->ID );
147
			return true;
148
		}
149
		return false;
150
	}
151
152
	/**
153
	 * Enqueues scripts and styles necessary for SSO login.
154
	 */
155
	public function login_enqueue_scripts() {
156
		global $action;
157
158
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
159
			return;
160
		}
161
162
		if ( is_rtl() ) {
163
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login-rtl.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
164
		} else {
165
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
166
		}
167
168
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
169
	}
170
171
	/**
172
	 * Enqueue styles neceessary for Jetpack SSO on users' profiles
173
	 */
174
	public function admin_enqueue_scripts() {
175
		$screen = get_current_screen();
176
177
		if ( empty( $screen ) || ! in_array( $screen->base, array( 'edit-user', 'profile' ) ) ) {
178
			return;
179
		}
180
181
		wp_enqueue_style( 'jetpack-sso-profile', plugins_url( 'modules/sso/jetpack-sso-profile.css', JETPACK__PLUGIN_FILE ), array( 'genericons' ), JETPACK__VERSION );
182
	}
183
184
	/**
185
	 * Adds Jetpack SSO classes to login body
186
	 *
187
	 * @param  array $classes Array of classes to add to body tag
188
	 * @return array          Array of classes to add to body tag
189
	 */
190
	public function login_body_class( $classes ) {
191
		global $action;
192
193
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
194
			return $classes;
195
		}
196
197
		// Always add the jetpack-sso class so that we can add SSO specific styling even when the SSO form isn't being displayed.
198
		$classes[] = 'jetpack-sso';
199
200
		/**
201
		 * Should we show the SSO login form?
202
		 *
203
		 * $_GET['jetpack-sso-default-form'] is used to provide a fallback in case JavaScript is not enabled.
204
		 *
205
		 * The default_to_sso_login() method allows us to dynamically decide whether we show the SSO login form or not.
206
		 * The SSO module uses the method to display the default login form if we can not find a user to log in via SSO.
207
		 * But, the method could be filtered by a site admin to always show the default login form if that is preferred.
208
		 */
209
		if ( empty( $_GET['jetpack-sso-show-default-form'] ) && Jetpack_SSO_Helpers::show_sso_login() ) {
210
			$classes[] = 'jetpack-sso-form-display';
211
		}
212
213
		return $classes;
214
	}
215
216
	/**
217
	 * Adds settings fields to Settings > General > Single Sign On that allows users to
218
	 * turn off the login form on wp-login.php
219
	 *
220
	 * @since 2.7
221
	 **/
222
	public function register_settings() {
223
224
		add_settings_section(
225
			'jetpack_sso_settings',
226
			__( 'Single Sign On' , 'jetpack' ),
227
			'__return_false',
228
			'jetpack-sso'
229
		);
230
231
		/*
232
		 * Settings > General > Single Sign On
233
		 * Require two step authentication
234
		 */
235
		register_setting(
236
			'jetpack-sso',
237
			'jetpack_sso_require_two_step',
238
			array( $this, 'validate_jetpack_sso_require_two_step' )
239
		);
240
241
		add_settings_field(
242
			'jetpack_sso_require_two_step',
243
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
244
			array( $this, 'render_require_two_step' ),
245
			'jetpack-sso',
246
			'jetpack_sso_settings'
247
		);
248
249
		/*
250
		 * Settings > General > Single Sign On
251
		 */
252
		register_setting(
253
			'jetpack-sso',
254
			'jetpack_sso_match_by_email',
255
			array( $this, 'validate_jetpack_sso_match_by_email' )
256
		);
257
258
		add_settings_field(
259
			'jetpack_sso_match_by_email',
260
			'', // __( 'Match by Email' , 'jetpack' ),
261
			array( $this, 'render_match_by_email' ),
262
			'jetpack-sso',
263
			'jetpack_sso_settings'
264
		);
265
	}
266
267
	/**
268
	 * Builds the display for the checkbox allowing user to require two step
269
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
270
	 *
271
	 * @since 2.7
272
	 **/
273
	public function render_require_two_step() {
274
		?>
275
		<label>
276
			<input
277
				type="checkbox"
278
				name="jetpack_sso_require_two_step"
279
				<?php checked( Jetpack_SSO_Helpers::is_two_step_required() ); ?>
280
				<?php disabled( Jetpack_SSO_Helpers::is_require_two_step_checkbox_disabled() ); ?>
281
			>
282
			<?php esc_html_e( 'Require Two-Step Authentication' , 'jetpack' ); ?>
283
		</label>
284
		<?php
285
	}
286
287
	/**
288
	 * Validate the require  two step checkbox in Settings > General
289
	 *
290
	 * @since 2.7
291
	 * @return boolean
292
	 **/
293
	public function validate_jetpack_sso_require_two_step( $input ) {
294
		return ( ! empty( $input ) ) ? 1 : 0;
295
	}
296
297
	/**
298
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
299
	 * Displays in Settings > General
300
	 *
301
	 * @since 2.9
302
	 **/
303
	public function render_match_by_email() {
304
		?>
305
			<label>
306
				<input
307
					type="checkbox"
308
					name="jetpack_sso_match_by_email"
309
					<?php checked( Jetpack_SSO_Helpers::match_by_email() ); ?>
310
					<?php disabled( Jetpack_SSO_Helpers::is_match_by_email_checkbox_disabled() ); ?>
311
				>
312
				<?php esc_html_e( 'Match by Email', 'jetpack' ); ?>
313
			</label>
314
		<?php
315
	}
316
317
	/**
318
	 * Validate the match by email check in Settings > General
319
	 *
320
	 * @since 2.9
321
	 * @return boolean
322
	 **/
323
	public function validate_jetpack_sso_match_by_email( $input ) {
324
		return ( ! empty( $input ) ) ? 1 : 0;
325
	}
326
327
	/**
328
	 * Checks to determine if the user wants to login on wp-login
329
	 *
330
	 * This function mostly exists to cover the exceptions to login
331
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
332
	 * does not have to exist. By default WordPress assumes login if an action
333
	 * is not set, however this may not be true, as in the case of logout
334
	 * where $_GET[loggedout] is instead set
335
	 *
336
	 * @return boolean
337
	 **/
338
	private function wants_to_login() {
339
		$wants_to_login = false;
340
341
		// Cover default WordPress behavior
342
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
343
344
		// And now the exceptions
345
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
346
347
		if ( 'login' == $action ) {
348
			$wants_to_login = true;
349
		}
350
351
		return $wants_to_login;
352
	}
353
354
	function login_init() {
355
		global $action;
356
357
		if ( Jetpack_SSO_Helpers::should_hide_login_form() ) {
358
			/**
359
			 * Since the default authenticate filters fire at priority 20 for checking username and password,
360
			 * let's fire at priority 30. wp_authenticate_spam_check is fired at priority 99, but since we return a
361
			 * WP_Error in disable_default_login_form, then we won't trigger spam processing logic.
362
			 */
363
			add_filter( 'authenticate', array( $this, 'disable_default_login_form' ), 30 );
364
365
			/**
366
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
367
			 *
368
			 * @module sso
369
			 *
370
			 * @since 2.8.0
371
			 *
372
			 * @param bool true Should the disclaimer be displayed. Default to true.
373
			 */
374
			$display_sso_disclaimer = apply_filters( 'jetpack_sso_display_disclaimer', true );
375
			if ( $display_sso_disclaimer ) {
376
				add_filter( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
377
			}
378
		}
379
380
		/**
381
		 * If the user is attempting to logout AND the auto-forward to WordPress.com
382
		 * login is set then we need to ensure we do not auto-forward the user and get
383
		 * them stuck in an infinite logout loop.
384
		 */
385
		if ( isset( $_GET['loggedout'] ) && Jetpack_SSO_Helpers::bypass_login_forward_wpcom() ) {
386
			add_filter( 'jetpack_remove_login_form', '__return_true' );
387
		}
388
389
		/**
390
		 * Check to see if the site admin wants to automagically forward the user
391
		 * to the WordPress.com login page AND  that the request to wp-login.php
392
		 * is not something other than login (Like logout!)
393
		 */
394 View Code Duplication
		if (
395
			$this->wants_to_login()
396
			&& Jetpack_SSO_Helpers::bypass_login_forward_wpcom()
397
		) {
398
			add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
399
			$this->maybe_save_cookie_redirect();
400
			$reauth = ! empty( $_GET['force_reauth'] );
401
			$sso_url = $this->get_sso_url_or_die( $reauth );
402
			JetpackTracking::record_user_event( 'sso_login_redirect_bypass_success' );
403
			wp_safe_redirect( $sso_url );
404
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method login_init() 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...
405
		}
406
407
		if ( 'login' === $action ) {
408
			$this->display_sso_login_form();
409
		} elseif ( 'jetpack-sso' === $action ) {
410
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
411
				$this->handle_login();
412
				$this->display_sso_login_form();
413
			} else {
414
				if ( Jetpack::check_identity_crisis() ) {
415
					JetpackTracking::record_user_event( 'sso_login_redirect_failed', array(
416
						'error_message' => 'identity_crisis'
417
					) );
418
					add_filter( 'login_message', array( $this, 'error_msg_identity_crisis' ) );
419 View Code Duplication
				} else {
420
					$this->maybe_save_cookie_redirect();
421
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
422
					add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
423
					$reauth = ! empty( $_GET['force_reauth'] );
424
					$sso_url = $this->get_sso_url_or_die( $reauth );
425
					JetpackTracking::record_user_event( 'sso_login_redirect_success' );
426
					wp_safe_redirect( $sso_url );
427
					exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method login_init() 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...
428
				}
429
			}
430
		}
431
	}
432
433
	/**
434
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
435
	 * up the hooks required to display the SSO form.
436
	 */
437
	public function display_sso_login_form() {
438
		if ( Jetpack::check_identity_crisis() ) {
439
			add_filter( 'login_message', array( $this, 'error_msg_identity_crisis' ) );
440
			return;
441
		}
442
443
		$sso_nonce = self::request_initial_nonce();
444
		if ( is_wp_error( $sso_nonce ) ) {
445
			return;
446
		}
447
448
		add_action( 'login_form',            array( $this, 'login_form' ) );
449
		add_filter( 'login_body_class',      array( $this, 'login_body_class' ) );
450
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
451
	}
452
453
	/**
454
	 * Conditionally save the redirect_to url as a cookie.
455
	 */
456
	public static function maybe_save_cookie_redirect() {
457
		if ( headers_sent() ) {
458
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
459
		}
460
461
		if ( ! empty( $_GET['redirect_to'] ) ) {
462
			// If we have something to redirect to
463
			$url = esc_url_raw( $_GET['redirect_to'] );
464
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
465
466
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
467
			// Otherwise, if it's already set, purge it.
468
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
469
		}
470
471
		if ( ! empty( $_GET['rememberme'] ) ) {
472
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
473
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
474
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
475
		}
476
	}
477
478
	/**
479
	 * Outputs the Jetpack SSO button and description as well as the toggle link
480
	 * for switching between Jetpack SSO and default login.
481
	 */
482
	function login_form() {
483
		$site_name = get_bloginfo( 'name' );
484
		if ( ! $site_name ) {
485
			$site_name = get_bloginfo( 'url' );
486
		}
487
488
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] )
489
			? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ]
490
			: false;
491
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] )
492
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ]
493
			: false;
494
495
		?>
496
		<div id="jetpack-sso-wrap">
497
			<?php if ( $display_name && $gravatar ) : ?>
498
				<div id="jetpack-sso-wrap__user">
499
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
500
501
					<h2>
502
						<?php
503
							echo wp_kses(
504
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
505
								array( 'span' => true )
506
							);
507
						?>
508
					</h2>
509
				</div>
510
511
			<?php endif; ?>
512
513
514
			<div id="jetpack-sso-wrap__action">
515
				<?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...
516
517
				<?php if ( $display_name && $gravatar ) : ?>
518
					<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
519
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
520
					</a>
521
				<?php else : ?>
522
					<p>
523
						<?php
524
							echo esc_html(
525
								sprintf(
526
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
527
									esc_html( $site_name )
528
								)
529
							);
530
						?>
531
					</p>
532
				<?php endif; ?>
533
			</div>
534
535
			<?php if ( ! Jetpack_SSO_Helpers::should_hide_login_form() ) : ?>
536
				<div class="jetpack-sso-or">
537
					<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
538
				</div>
539
540
				<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '1' ) ); ?>" class="jetpack-sso-toggle wpcom">
541
					<?php
542
						esc_html_e( 'Log in with username and password', 'jetpack' )
543
					?>
544
				</a>
545
546
				<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '0' ) ); ?>" class="jetpack-sso-toggle default">
547
					<?php
548
						esc_html_e( 'Log in with WordPress.com', 'jetpack' )
549
					?>
550
				</a>
551
			<?php endif; ?>
552
		</div>
553
		<?php
554
	}
555
556
	/**
557
	 * Clear the cookies that store the profile information for the last
558
	 * WPCOM user to connect.
559
	 */
560
	static function clear_wpcom_profile_cookies() {
561 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
562
			setcookie(
563
				'jetpack_sso_wpcom_name_' . COOKIEHASH,
564
				' ',
565
				time() - YEAR_IN_SECONDS,
566
				COOKIEPATH,
567
				COOKIE_DOMAIN
568
			);
569
		}
570
571 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
572
			setcookie(
573
				'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
574
				' ',
575
				time() - YEAR_IN_SECONDS,
576
				COOKIEPATH,
577
				COOKIE_DOMAIN
578
			);
579
		}
580
	}
581
582
	static function delete_connection_for_user( $user_id ) {
583
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
584
			return;
585
		}
586
		Jetpack::load_xml_rpc_client();
587
		$xml = new Jetpack_IXR_Client( array(
588
			'wpcom_user_id' => $user_id,
589
		) );
590
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
591
592
		if ( $xml->isError() ) {
593
			return false;
594
		}
595
596
		// Clean up local data stored for SSO
597
		delete_user_meta( $user_id, 'wpcom_user_id' );
598
		delete_user_meta( $user_id, 'wpcom_user_data'  );
599
		self::clear_wpcom_profile_cookies();
600
601
		return $xml->getResponse();
602
	}
603
604 View Code Duplication
	static function request_initial_nonce() {
605
		Jetpack::load_xml_rpc_client();
606
		$xml = new Jetpack_IXR_Client( array(
607
			'user_id' => get_current_user_id(),
608
		) );
609
		$xml->query( 'jetpack.sso.requestNonce' );
610
611
		if ( $xml->isError() ) {
612
			return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
613
		}
614
615
		return $xml->getResponse();
616
	}
617
618
	/**
619
	 * The function that actually handles the login!
620
	 */
621
	function handle_login() {
622
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
623
		$wpcom_user_id = (int) $_GET['user_id'];
624
625
		Jetpack::load_xml_rpc_client();
626
		$xml = new Jetpack_IXR_Client( array(
627
			'user_id' => get_current_user_id(),
628
		) );
629
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
630
631
		if ( $xml->is_error() || empty( $user_data = $xml->getResponse() ) ) {
632
			add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
633
			add_filter( 'login_message', array( $this, 'error_invalid_response_data' ) );
634
			return;
635
		}
636
637
		$user_data = (object) $user_data;
638
		$user = null;
639
640
		/**
641
		 * Fires before Jetpack's SSO modifies the log in form.
642
		 *
643
		 * @module sso
644
		 *
645
		 * @since 2.6.0
646
		 *
647
		 * @param object $user_data WordPress.com User information.
648
		 */
649
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
650
651
		if ( Jetpack_SSO_Helpers::is_two_step_required() && 0 === (int) $user_data->two_step_enabled ) {
652
			$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...
653
654
			JetpackTracking::record_user_event( 'sso_login_failed', array(
655
				'error_message' => 'error_msg_enable_two_step'
656
			) );
657
658
			/** This filter is documented in core/src/wp-includes/pluggable.php */
659
			do_action( 'wp_login_failed', $user_data->login );
660
			add_filter( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
661
			return;
662
		}
663
664
		$user_found_with = '';
665
		if ( empty( $user ) && isset( $user_data->external_user_id ) ) {
666
			$user_found_with = 'external_user_id';
667
			$user = get_user_by( 'id', intval( $user_data->external_user_id ) );
668
			if ( $user ) {
669
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
670
			}
671
		}
672
673
		// If we don't have one by wpcom_user_id, try by the email?
674
		if ( empty( $user ) && Jetpack_SSO_Helpers::match_by_email() ) {
675
			$user_found_with = 'match_by_email';
676
			$user = get_user_by( 'email', $user_data->email );
677
			if ( $user ) {
678
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
679
			}
680
		}
681
682
		// If we've still got nothing, create the user.
683
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || Jetpack_SSO_Helpers::new_user_override() ) ) {
684
			// If not matching by email we still need to verify the email does not exist
685
			// or this blows up
686
			/**
687
			 * If match_by_email is true, we know the email doesn't exist, as it would have
688
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
689
			 * user, then we know that email is unused, so it's safe to add.
690
			 */
691
			if ( Jetpack_SSO_Helpers::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
692
				$username = $user_data->login;
693
694
				if ( username_exists( $username ) ) {
695
					$username = $user_data->login . '_' . $user_data->ID;
696
				}
697
698
				$tries = 0;
699
				while ( username_exists( $username ) ) {
700
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
701 View Code Duplication
					if ( $tries++ >= 5 ) {
702
						JetpackTracking::record_user_event( 'sso_login_failed', array(
703
							'error_message' => 'could_not_create_username'
704
						) );
705
						add_filter( 'login_message', array( $this, 'error_unable_to_create_user' ) );
706
						return;
707
					}
708
				}
709
710
				$user_found_with = Jetpack_SSO_Helpers::new_user_override()
711
					? 'user_created_new_user_override'
712
					: 'user_created_users_can_register';
713
714
				$password = wp_generate_password( 20 );
715
				$user_id  = wp_create_user( $username, $password, $user_data->email );
716
				$user     = get_userdata( $user_id );
717
718
				$user->display_name = $user_data->display_name;
719
				$user->first_name   = $user_data->first_name;
720
				$user->last_name    = $user_data->last_name;
721
				$user->url          = $user_data->url;
722
				$user->description  = $user_data->description;
723
				wp_update_user( $user );
724
725
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
726 View Code Duplication
			} else {
727
				JetpackTracking::record_user_event( 'sso_login_failed', array(
728
					'error_message' => 'error_msg_email_already_exists'
729
				) );
730
731
				$this->user_data = $user_data;
732
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
733
				return;
734
			}
735
		}
736
737
		/**
738
		 * Fires after we got login information from WordPress.com.
739
		 *
740
		 * @module sso
741
		 *
742
		 * @since 2.6.0
743
		 *
744
		 * @param array  $user      Local User information.
745
		 * @param object $user_data WordPress.com User Login information.
746
		 */
747
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
748
749
		if ( $user ) {
750
			// Cache the user's details, so we can present it back to them on their user screen
751
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
752
753
			$remember = false;
754 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
755
				$remember = true;
756
				// And then purge it
757
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
758
			}
759
			/**
760
			 * Filter the remember me value.
761
			 *
762
			 * @module sso
763
			 *
764
			 * @since 2.8.0
765
			 *
766
			 * @param bool $remember Is the remember me option checked?
767
			 */
768
			$remember = apply_filters( 'jetpack_remember_login', $remember );
769
			wp_set_auth_cookie( $user->ID, $remember );
770
771
			/** This filter is documented in core/src/wp-includes/user.php */
772
			do_action( 'wp_login', $user->user_login, $user );
773
774
			wp_set_current_user( $user->ID );
775
776
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
777
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
778
779
			// If we have a saved redirect to request in a cookie
780 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
781
				// Set that as the requested redirect to
782
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
783
				// And then purge it
784
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
785
			}
786
787
			$is_user_connected = Jetpack::is_user_connected( $user->ID );
788
			JetpackTracking::record_user_event( 'sso_user_logged_in', array(
789
				'user_found_with' => $user_found_with,
790
				'user_connected'  => (bool) $is_user_connected,
791
				'user_role'       => Jetpack::translate_current_user_to_role()
792
			) );
793
794
			if ( ! $is_user_connected ) {
795
				$calypso_env = ! empty( $_GET['calypso_env'] )
796
					? sanitize_key( $_GET['calypso_env'] )
797
					: '';
798
799
				wp_safe_redirect(
800
					add_query_arg(
801
						array(
802
							'redirect_to'               => $redirect_to,
803
							'request_redirect_to'       => $_request_redirect_to,
804
							'calypso_env'               => $calypso_env,
805
							'jetpack-sso-auth-redirect' => '1',
806
						),
807
						admin_url()
808
					)
809
				);
810
				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...
811
			}
812
813
			wp_safe_redirect(
814
				/** This filter is documented in core/src/wp-login.php */
815
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
816
			);
817
			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...
818
		}
819
820
		add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
821
822
		JetpackTracking::record_user_event( 'sso_login_failed', array(
823
			'error_message' => 'cant_find_user'
824
		) );
825
826
		$this->user_data = $user_data;
827
		/** This filter is documented in core/src/wp-includes/pluggable.php */
828
		do_action( 'wp_login_failed', $user_data->login );
829
		add_filter( 'login_message', array( $this, 'cant_find_user' ) );
830
	}
831
832
	static function profile_page_url() {
833
		return admin_url( 'profile.php' );
834
	}
835
836
	/**
837
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
838
	 *
839
	 * @param  array   $args       An array of arguments to add to the SSO URL.
840
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
841
	 * @return string              Returns the HTML markup for the button.
842
	 */
843
	function build_sso_button( $args = array(), $is_primary = false ) {
844
		$url = $this->build_sso_button_url( $args );
845
		$classes = $is_primary
846
			? 'jetpack-sso button button-primary'
847
			: 'jetpack-sso button';
848
849
		return sprintf(
850
			'<a rel="nofollow" href="%1$s" class="%2$s"><span>%3$s %4$s</span></a>',
851
			esc_url( $url ),
852
			$classes,
853
			'<span class="genericon genericon-wordpress"></span>',
854
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
855
		);
856
	}
857
858
	/**
859
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
860
	 *
861
	 * @param  array  $args An array of arguments to add to the SSO URL.
862
	 * @return string       The URL used for SSO.
863
	 */
864
	function build_sso_button_url( $args = array() ) {
865
		$defaults = array(
866
			'action'  => 'jetpack-sso',
867
		);
868
869
		$args = wp_parse_args( $args, $defaults );
870
871
		if ( ! empty( $_GET['redirect_to'] ) ) {
872
			$args['redirect_to'] = urlencode( esc_url_raw( $_GET['redirect_to'] ) );
873
		}
874
875
		return add_query_arg( $args, wp_login_url() );
876
	}
877
878
	/**
879
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
880
	 *
881
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
882
	 * @param  array    $args    Optional query parameters.
883
	 * @return string            The WordPress.com SSO URL.
884
	 */
885
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
886
		if ( empty( $reauth ) ) {
887
			$sso_redirect = $this->build_sso_url( $args );
888
		} else {
889
			self::clear_wpcom_profile_cookies();
890
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
891
		}
892
893
		// If there was an error retrieving the SSO URL, then error.
894
		if ( is_wp_error( $sso_redirect ) ) {
895
			$error_message = sanitize_text_field(
896
				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...
897
			);
898
			JetpackTracking::record_user_event( 'sso_login_redirect_failed', array(
899
				'error_message' => $error_message
900
			) );
901
			wp_die( $error_message );
902
		}
903
904
		return $sso_redirect;
905
	}
906
907
	/**
908
	 * Build WordPress.com SSO URL with appropriate query parameters.
909
	 *
910
	 * @param  array  $args Optional query parameters.
911
	 * @return string       WordPress.com SSO URL
912
	 */
913
	function build_sso_url( $args = array() ) {
914
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
915
		$defaults = array(
916
			'action'       => 'jetpack-sso',
917
			'site_id'      => Jetpack_Options::get_option( 'id' ),
918
			'sso_nonce'    => $sso_nonce,
919
			'calypso_auth' => '1',
920
		);
921
922
		$args = wp_parse_args( $args, $defaults );
923
924
		if ( is_wp_error( $args['sso_nonce'] ) ) {
925
			return $args['sso_nonce'];
926
		}
927
928
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
929
	}
930
931
	/**
932
	 * Build WordPress.com SSO URL with appropriate query parameters,
933
	 * including the parameters necessary to force the user to reauthenticate
934
	 * on WordPress.com.
935
	 *
936
	 * @param  array  $args Optional query parameters.
937
	 * @return string       WordPress.com SSO URL
938
	 */
939
	function build_reauth_and_sso_url( $args = array() ) {
940
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
941
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
942
943
		if ( is_wp_error( $redirect ) ) {
944
			return $redirect;
945
		}
946
947
		$defaults = array(
948
			'action'       => 'jetpack-sso',
949
			'site_id'      => Jetpack_Options::get_option( 'id' ),
950
			'sso_nonce'    => $sso_nonce,
951
			'reauth'       => '1',
952
			'redirect_to'  => urlencode( $redirect ),
953
			'calypso_auth' => '1',
954
		);
955
956
		$args = wp_parse_args( $args, $defaults );
957
958
		if ( is_wp_error( $args['sso_nonce'] ) ) {
959
			return $args['sso_nonce'];
960
		}
961
962
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
963
	}
964
965
	/**
966
	 * Determines local user associated with a given WordPress.com user ID.
967
	 *
968
	 * @since 2.6.0
969
	 *
970
	 * @param int $wpcom_user_id User ID from WordPress.com
971
	 * @return object Local user object if found, null if not.
972
	 */
973
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
974
		$user_query = new WP_User_Query( array(
975
			'meta_key'   => 'wpcom_user_id',
976
			'meta_value' => intval( $wpcom_user_id ),
977
			'number'     => 1,
978
		) );
979
980
		$users = $user_query->get_results();
981
		return $users ? array_shift( $users ) : null;
982
	}
983
984
	/**
985
	 * Error message displayed on the login form when two step is required and
986
	 * the user's account on WordPress.com does not have two step enabled.
987
	 *
988
	 * @since 2.7
989
	 * @param string $message
990
	 * @return string
991
	 **/
992
	public function error_msg_enable_two_step( $message ) {
993
		$error = sprintf(
994
			wp_kses(
995
				__(
996
					'Two-Step Authentication is required to access this site. Please visit your <a href="%1$s" target="_blank">Security Settings</a> to configure <a href="%2$s" target="_blank">Two-step Authentication</a> for your account.',
997
					'jetpack'
998
				),
999
				array(  'a' => array( 'href' => array() ) )
1000
			),
1001
			'https://wordpress.com/me/security/two-step',
1002
			'https://support.wordpress.com/security/two-step-authentication/'
1003
		);
1004
1005
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1006
1007
		return $message;
1008
	}
1009
1010
	/**
1011
	 * Error message displayed when the user tries to SSO, but match by email
1012
	 * is off and they already have an account with their email address on
1013
	 * this site.
1014
	 *
1015
	 * @param string $message
1016
	 * @return string
1017
	 */
1018
	public function error_msg_email_already_exists( $message ) {
1019
		$error = sprintf(
1020
			wp_kses(
1021
				__(
1022
					'You already have an account on this site. Please <a href="%1$s">sign in</a> with your username and password and then connect to WordPress.com.',
1023
					'jetpack'
1024
				),
1025
				array(  'a' => array( 'href' => array() ) )
1026
			),
1027
			esc_url_raw( add_query_arg( 'jetpack-sso-show-default-form', '1', wp_login_url() ) )
1028
		);
1029
1030
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1031
1032
		return $message;
1033
	}
1034
1035
	/**
1036
	 * Error message that is displayed when the current site is in an identity crisis and SSO can not be used.
1037
	 *
1038
	 * @since 4.4.0
1039
	 *
1040
	 * @param $message
1041
	 *
1042
	 * @return string
1043
	 */
1044
	public function error_msg_identity_crisis( $message ) {
1045
		$error = esc_html__( 'Logging in with WordPress.com is not currently available because this site is experiencing connection problems.', 'jetpack' );
1046
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1047
		return $message;
1048
	}
1049
1050
	/**
1051
	 * Error message that is displayed when we are not able to verify the SSO nonce due to an XML error or
1052
	 * failed validation. In either case, we prompt the user to try again or log in with username and password.
1053
	 *
1054
	 * @since 4.4.0
1055
	 *
1056
	 * @param $message
1057
	 *
1058
	 * @return string
1059
	 */
1060
	public function error_invalid_response_data( $message ) {
1061
		$error = esc_html__(
1062
			'There was an error logging you in via WordPress.com, please try again or try logging in with your username and password.',
1063
			'jetpack'
1064
		);
1065
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1066
		return $message;
1067
	}
1068
1069
	/**
1070
	 * Error message that is displayed when we were not able to automatically create an account for a user
1071
	 * after a user has logged in via SSO. By default, this message is triggered after trying to create an account 5 times.
1072
	 *
1073
	 * @since 4.4.0
1074
	 *
1075
	 * @param $message
1076
	 *
1077
	 * @return string
1078
	 */
1079
	public function error_unable_to_create_user( $message ) {
1080
		$error = esc_html__(
1081
			'There was an error creating a user for you. Please contact the administrator of your site.',
1082
			'jetpack'
1083
		);
1084
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1085
		return $message;
1086
	}
1087
1088
	/**
1089
	 * Builds the translation ready string that is to be used when the site hides the default login form.
1090
	 *
1091
	 * @since 4.1.0
1092
	 * @return string
1093
	 */
1094
	public function get_sso_required_message() {
1095
		$msg = esc_html__( 'A WordPress.com account is required to access this site. Click the button below to sign in or create a free WordPress.com account.', 'jetpack' );
1096
1097
		/**
1098
		 * Filter the message displayed when the default WordPress login form is disabled.
1099
		 *
1100
		 * @module sso
1101
		 *
1102
		 * @since 2.8.0
1103
		 *
1104
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
1105
		 */
1106
		return apply_filters( 'jetpack_sso_disclaimer_message', $msg );
1107
	}
1108
1109
	/**
1110
	 * Message displayed when the site admin has disabled the default WordPress
1111
	 * login form in Settings > General > Single Sign On
1112
	 *
1113
	 * @since 2.7
1114
	 * @param string $message
1115
	 *
1116
	 * @return string
1117
	 **/
1118
	public function msg_login_by_jetpack( $message ) {
1119
		$msg = $this->get_sso_required_message();
1120
1121
		if ( empty( $msg ) ) {
1122
			return $message;
1123
		}
1124
1125
		$message .= sprintf( '<p class="message">%s</p>', $msg );
1126
		return $message;
1127
	}
1128
1129
	/**
1130
	 * Message displayed when the user can not be found after approving the SSO process on WordPress.com
1131
	 *
1132
	 * @param string $message
1133
	 * @return string
1134
	 */
1135
	function cant_find_user( $message ) {
1136
		$error = esc_html__(
1137
			"We couldn't find your account. If you already have an account, make sure you have connected to WordPress.com.",
1138
			'jetpack'
1139
		);
1140
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1141
1142
		return $message;
1143
	}
1144
1145
	/**
1146
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
1147
	 * WordPress.com authorization flow.
1148
	 *
1149
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1150
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1151
	 */
1152
	function maybe_authorize_user_after_sso() {
1153
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1154
			return;
1155
		}
1156
1157
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url();
1158
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to;
1159
1160
		/** This filter is documented in core/src/wp-login.php */
1161
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
1162
1163
		/**
1164
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1165
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1166
		 */
1167
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1168
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1169
1170
		/**
1171
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1172
		 */
1173
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1174
1175
		add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
1176
		wp_safe_redirect( $connect_url );
1177
		exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method maybe_authorize_user_after_sso() 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...
1178
	}
1179
1180
	/**
1181
	 * Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are
1182
	 * stored when the user logs out, and then deleted when the user logs in.
1183
	 */
1184
	function store_wpcom_profile_cookies_on_logout() {
1185
		if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) {
1186
			return;
1187
		}
1188
1189
		$user_data = $this->get_user_data( get_current_user_id() );
1190
		if ( ! $user_data ) {
1191
			return;
1192
		}
1193
1194
		setcookie(
1195
			'jetpack_sso_wpcom_name_' . COOKIEHASH,
1196
			$user_data->display_name,
1197
			time() + WEEK_IN_SECONDS,
1198
			COOKIEPATH,
1199
			COOKIE_DOMAIN
1200
		);
1201
1202
		setcookie(
1203
			'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
1204
			get_avatar_url(
1205
				$user_data->email,
1206
				array( 'size' => 144, 'default' => 'mystery' )
1207
			),
1208
			time() + WEEK_IN_SECONDS,
1209
			COOKIEPATH,
1210
			COOKIE_DOMAIN
1211
		);
1212
	}
1213
1214
	/**
1215
	 * Determines if a local user is connected to WordPress.com
1216
	 *
1217
	 * @since 2.8
1218
	 * @param integer $user_id - Local user id
1219
	 * @return boolean
1220
	 **/
1221
	public function is_user_connected( $user_id ) {
1222
		return $this->get_user_data( $user_id );
1223
	}
1224
1225
	/**
1226
	 * Retrieves a user's WordPress.com data
1227
	 *
1228
	 * @since 2.8
1229
	 * @param integer $user_id - Local user id
1230
	 * @return mixed null or stdClass
1231
	 **/
1232
	public function get_user_data( $user_id ) {
1233
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1234
	}
1235
}
1236
1237
Jetpack_SSO::get_instance();
1238