Completed
Push — update/grey-out-elements-unava... ( 06b29b...1fb283 )
by
unknown
16:50 queued 06:18
created

Jetpack_SSO::get_sso_required_message()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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