Completed
Push — fix/photon-ssl-notice-3855 ( 27537b...a03632 )
by
unknown
263:26 queued 253:32
created

Jetpack_SSO   D

Complexity

Total Complexity 134

Size/Duplication

Total Lines 1154
Duplicated Lines 7.02 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 134
c 3
b 0
f 0
lcom 1
cbo 5
dl 81
loc 1154
rs 4

44 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 42 3
A get_instance() 0 7 2
A module_configure_button() 0 6 1
A module_configuration_load() 0 1 1
A module_configuration_head() 0 1 1
A module_configuration_screen() 0 9 1
A disable_default_login_form() 0 12 2
A maybe_logout_user() 0 11 2
A xmlrpc_methods() 0 4 1
A xmlrpc_user_disconnect() 0 18 2
A login_enqueue_scripts() 0 10 2
A admin_enqueue_scripts() 0 9 3
A login_body_class() 0 15 4
B register_settings() 0 44 1
A validate_jetpack_sso_require_two_step() 0 3 2
A validate_jetpack_sso_match_by_email() 0 3 2
A wants_to_login() 0 15 4
C login_init() 22 55 10
A display_sso_login_form() 0 10 2
B maybe_save_cookie_redirect() 0 21 6
C login_form() 0 73 9
A clear_wpcom_profile_cookies() 18 21 3
A delete_connection_for_user() 0 21 3
A request_initial_nonce() 13 13 2
F handle_login() 11 220 27
A profile_page_url() 0 3 1
A allowed_redirect_hosts() 0 9 2
A build_sso_button() 0 13 2
A build_sso_button_url() 0 13 2
A get_sso_url_or_die() 0 21 3
A build_sso_url() 0 16 3
B build_reauth_and_sso_url() 0 24 4
A get_user_by_wpcom_id() 0 10 2
A error_msg_enable_two_step() 0 17 1
A error_msg_email_already_exists() 0 16 1
A get_sso_required_message() 0 14 1
A msg_login_by_jetpack() 0 10 2
A cant_find_user() 0 9 1
B maybe_authorize_user_after_sso() 0 27 4
B store_wpcom_profile_cookies_on_logout() 0 29 3
A is_user_connected() 0 3 1
A get_user_data() 0 3 1
A render_require_two_step() 9 9 2
A render_match_by_email() 8 8 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_SSO often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_SSO, and based on these observations, apply Extract Interface, too.

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
		if ( Jetpack_SSO_Helpers::should_hide_login_form() ) {
41
			/**
42
			 * Since the default authenticate filters fire at priority 20 for checking username and password,
43
			 * let's fire at priority 30. wp_authenticate_spam_check is fired at priority 99, but since we return a
44
			 * WP_Error in disable_default_login_form, then we won't trigger spam processing logic.
45
			 */
46
			add_filter( 'authenticate', array( $this, 'disable_default_login_form' ), 30 );
47
48
			/**
49
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
50
			 *
51
			 * @module sso
52
			 *
53
			 * @since 2.8.0
54
			 *
55
			 * @param bool true Should the disclaimer be displayed. Default to true.
56
			 */
57
			$display_sso_disclaimer = apply_filters( 'jetpack_sso_display_disclaimer', true );
58
			if ( $display_sso_disclaimer ) {
59
				add_filter( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
60
			}
61
		}
62
	}
63
64
	/**
65
	 * Returns the single instance of the Jetpack_SSO object
66
	 *
67
	 * @since 2.8
68
	 * @return Jetpack_SSO
69
	 **/
70
	public static function get_instance() {
71
		if ( ! is_null( self::$instance ) ) {
72
			return self::$instance;
73
		}
74
75
		return self::$instance = new Jetpack_SSO;
76
	}
77
78
	/**
79
	 * Add configure button and functionality to the module card on the Jetpack screen
80
	 **/
81
	public static function module_configure_button() {
82
		Jetpack::enable_module_configurable( __FILE__ );
83
		Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
84
		Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
85
		Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
86
	}
87
88
	public static function module_configuration_load() {}
89
90
	public static function module_configuration_head() {}
91
92
	public static function module_configuration_screen() {
93
		?>
94
		<form method="post" action="options.php">
95
			<?php settings_fields( 'jetpack-sso' ); ?>
96
			<?php do_settings_sections( 'jetpack-sso' ); ?>
97
			<?php submit_button(); ?>
98
		</form>
99
		<?php
100
	}
101
102
103
	/**
104
	 * When the default login form is hidden, this method is called on the 'authenticate' filter with a priority of 30.
105
	 * This method disables the ability to submit the default login form.
106
	 *
107
	 * @param $user
108
	 *
109
	 * @return WP_Error
110
	 */
111
	public function disable_default_login_form( $user ) {
112
		if ( is_wp_error( $user ) ) {
113
			return $user;
114
		}
115
116
		/**
117
		 * Since we're returning an error that will be shown as a red notice, let's remove the
118
		 * informational "blue" notice.
119
		 */
120
		remove_filter( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
121
		return new WP_Error( 'jetpack_sso_required', $this->get_sso_required_message() );
122
	}
123
124
	/**
125
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
126
	 * to logout and reauthenticate with the site.
127
	 **/
128
	public function maybe_logout_user() {
129
		global $current_user;
130
131
		if ( 1 == $current_user->jetpack_force_logout ) {
132
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
133
			self::delete_connection_for_user( $current_user->ID );
134
			wp_logout();
135
			wp_safe_redirect( wp_login_url() );
136
			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...
137
		}
138
	}
139
140
141
	/**
142
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
143
	 *
144
	 * @param array $methods
145
	 * @return array
146
	 **/
147
	public function xmlrpc_methods( $methods ) {
148
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
149
		return $methods;
150
	}
151
152
	/**
153
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
154
	 * the next time the user visits the site.
155
	 **/
156
	public function xmlrpc_user_disconnect( $user_id ) {
157
		$user_query = new WP_User_Query(
158
			array(
159
				'meta_key' => 'wpcom_user_id',
160
				'meta_value' => $user_id,
161
			)
162
		);
163
		$user = $user_query->get_results();
164
		$user = $user[0];
165
166
		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...
167
			$user = wp_set_current_user( $user->ID );
168
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
169
			self::delete_connection_for_user( $user->ID );
170
			return true;
171
		}
172
		return false;
173
	}
174
175
	/**
176
	 * Enqueues scripts and styles necessary for SSO login.
177
	 */
178
	public function login_enqueue_scripts() {
179
		global $action;
180
181
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
182
			return;
183
		}
184
185
		wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
186
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
187
	}
188
189
	/**
190
	 * Enqueue styles neceessary for Jetpack SSO on users' profiles
191
	 */
192
	public function admin_enqueue_scripts() {
193
		$screen = get_current_screen();
194
195
		if ( empty( $screen ) || ! in_array( $screen->base, array( 'edit-user', 'profile' ) ) ) {
196
			return;
197
		}
198
199
		wp_enqueue_style( 'jetpack-sso-profile', plugins_url( 'modules/sso/jetpack-sso-profile.css', JETPACK__PLUGIN_FILE ), array( 'genericons' ), JETPACK__VERSION );
200
	}
201
202
	/**
203
	 * Adds Jetpack SSO classes to login body
204
	 *
205
	 * @param  array $classes Array of classes to add to body tag
206
	 * @return array          Array of classes to add to body tag
207
	 */
208
	public function login_body_class( $classes ) {
209
		global $action;
210
211
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
212
			return $classes;
213
		}
214
215
		// If jetpack-sso-default-form, show the default login form.
216
		if ( isset( $_GET['jetpack-sso-default-form'] ) && 1 == $_GET['jetpack-sso-default-form'] ) {
217
			return $classes;
218
		}
219
220
		$classes[] = 'jetpack-sso-body';
221
		return $classes;
222
	}
223
224
	/**
225
	 * Adds settings fields to Settings > General > Single Sign On that allows users to
226
	 * turn off the login form on wp-login.php
227
	 *
228
	 * @since 2.7
229
	 **/
230
	public function register_settings() {
231
232
		add_settings_section(
233
			'jetpack_sso_settings',
234
			__( 'Single Sign On' , 'jetpack' ),
235
			'__return_false',
236
			'jetpack-sso'
237
		);
238
239
		/*
240
		 * Settings > General > Single Sign On
241
		 * Require two step authentication
242
		 */
243
		register_setting(
244
			'jetpack-sso',
245
			'jetpack_sso_require_two_step',
246
			array( $this, 'validate_jetpack_sso_require_two_step' )
247
		);
248
249
		add_settings_field(
250
			'jetpack_sso_require_two_step',
251
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
252
			array( $this, 'render_require_two_step' ),
253
			'jetpack-sso',
254
			'jetpack_sso_settings'
255
		);
256
257
		/*
258
		 * Settings > General > Single Sign On
259
		 */
260
		register_setting(
261
			'jetpack-sso',
262
			'jetpack_sso_match_by_email',
263
			array( $this, 'validate_jetpack_sso_match_by_email' )
264
		);
265
266
		add_settings_field(
267
			'jetpack_sso_match_by_email',
268
			'', // __( 'Match by Email' , 'jetpack' ),
269
			array( $this, 'render_match_by_email' ),
270
			'jetpack-sso',
271
			'jetpack_sso_settings'
272
		);
273
	}
274
275
	/**
276
	 * Builds the display for the checkbox allowing user to require two step
277
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
278
	 *
279
	 * @since 2.7
280
	 **/
281 View Code Duplication
	public function render_require_two_step() {
282
		/** This filter is documented in modules/sso.php */
283
		$require_two_step = Jetpack_SSO_Helpers::is_two_step_required();
284
		$disabled = $require_two_step ? ' disabled="disabled"' : '';
285
		echo '<label>';
286
		echo '<input type="checkbox" name="jetpack_sso_require_two_step" ' . checked( $require_two_step, true, false ) . "$disabled>";
287
		esc_html_e( 'Require Two-Step Authentication' , 'jetpack' );
288
		echo '</label>';
289
	}
290
291
	/**
292
	 * Validate the require  two step checkbox in Settings > General
293
	 *
294
	 * @since 2.7
295
	 * @return boolean
296
	 **/
297
	public function validate_jetpack_sso_require_two_step( $input ) {
298
		return ( ! empty( $input ) ) ? 1 : 0;
299
	}
300
301
	/**
302
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
303
	 * Displays in Settings > General
304
	 *
305
	 * @since 2.9
306
	 **/
307 View Code Duplication
	public function render_match_by_email() {
308
		$match_by_email = 1 == $this->match_by_email();
0 ignored issues
show
Bug introduced by
The method match_by_email() does not exist on Jetpack_SSO. Did you maybe mean render_match_by_email()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
309
		$disabled = $match_by_email ? ' disabled="disabled"' : '';
310
		echo '<label>';
311
		echo '<input type="checkbox" name="jetpack_sso_match_by_email"' . checked( $match_by_email, true, false ) . "$disabled>";
312
		esc_html_e( 'Match by Email', 'jetpack' );
313
		echo '</label>';
314
	}
315
316
	/**
317
	 * Validate the match by email check in Settings > General
318
	 *
319
	 * @since 2.9
320
	 * @return boolean
321
	 **/
322
	public function validate_jetpack_sso_match_by_email( $input ) {
323
		return ( ! empty( $input ) ) ? 1 : 0;
324
	}
325
326
	/**
327
	 * Checks to determine if the user wants to login on wp-login
328
	 *
329
	 * This function mostly exists to cover the exceptions to login
330
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
331
	 * does not have to exist. By default WordPress assumes login if an action
332
	 * is not set, however this may not be true, as in the case of logout
333
	 * where $_GET[loggedout] is instead set
334
	 *
335
	 * @return boolean
336
	 **/
337
	private function wants_to_login() {
338
		$wants_to_login = false;
339
340
		// Cover default WordPress behavior
341
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
342
343
		// And now the exceptions
344
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
345
346
		if ( 'login' == $action ) {
347
			$wants_to_login = true;
348
		}
349
350
		return $wants_to_login;
351
	}
352
353
	function login_init() {
354
		global $action;
355
356
		/**
357
		 * If the user is attempting to logout AND the auto-forward to WordPress.com
358
		 * login is set then we need to ensure we do not auto-forward the user and get
359
		 * them stuck in an infinite logout loop.
360
		 */
361
		if ( isset( $_GET['loggedout'] ) && Jetpack_SSO_Helpers::bypass_login_forward_wpcom() ) {
362
			add_filter( 'jetpack_remove_login_form', '__return_true' );
363
		}
364
365
		/**
366
		 * Check to see if the site admin wants to automagically forward the user
367
		 * to the WordPress.com login page AND  that the request to wp-login.php
368
		 * is not something other than login (Like logout!)
369
		 */
370 View Code Duplication
		if (
371
			$this->wants_to_login()
372
			&& Jetpack_SSO_Helpers::bypass_login_forward_wpcom()
373
		) {
374
			add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
375
			$this->maybe_save_cookie_redirect();
376
			$reauth = ! empty( $_GET['force_reauth'] );
377
			$sso_url = $this->get_sso_url_or_die( $reauth );
378
			JetpackTracking::record_user_event( 'sso_login_redirect_bypass_success' );
379
			wp_safe_redirect( $sso_url );
380
			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...
381
		}
382
383
		if ( 'login' === $action ) {
384
			$this->display_sso_login_form();
385
		} elseif ( 'jetpack-sso' === $action ) {
386
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
387
				$this->handle_login();
388
				$this->display_sso_login_form();
389
			} else {
390
				if ( Jetpack::check_identity_crisis() ) {
391
					JetpackTracking::record_user_event( 'sso_login_redirect_failed', array(
392
						'error_message' => 'identity_crisis'
393
					) );
394
					wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
395 View Code Duplication
				} else {
396
					$this->maybe_save_cookie_redirect();
397
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
398
					add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
399
					$reauth = ! empty( $_GET['force_reauth'] );
400
					$sso_url = $this->get_sso_url_or_die( $reauth );
401
					JetpackTracking::record_user_event( 'sso_login_redirect_success' );
402
					wp_safe_redirect( $sso_url );
403
					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...
404
				}
405
			}
406
		}
407
	}
408
409
	/**
410
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
411
	 * up the hooks required to display the SSO form.
412
	 */
413
	public function display_sso_login_form() {
414
		$sso_nonce = self::request_initial_nonce();
415
		if ( is_wp_error( $sso_nonce ) ) {
416
			return;
417
		}
418
419
		add_action( 'login_form',            array( $this, 'login_form' ) );
420
		add_filter( 'login_body_class',      array( $this, 'login_body_class' ) );
421
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
422
	}
423
424
	/**
425
	 * Conditionally save the redirect_to url as a cookie.
426
	 */
427
	public static function maybe_save_cookie_redirect() {
428
		if ( headers_sent() ) {
429
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
430
		}
431
432
		if ( ! empty( $_GET['redirect_to'] ) ) {
433
			// If we have something to redirect to
434
			$url = esc_url_raw( $_GET['redirect_to'] );
435
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
436
437
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
438
			// Otherwise, if it's already set, purge it.
439
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
440
		}
441
442
		if ( ! empty( $_GET['rememberme'] ) ) {
443
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
444
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
445
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
446
		}
447
	}
448
449
	/**
450
	 * Outputs the Jetpack SSO button and description as well as the toggle link
451
	 * for switching between Jetpack SSO and default login.
452
	 */
453
	function login_form() {
454
		$site_name = get_bloginfo( 'name' );
455
		if ( ! $site_name ) {
456
			$site_name = get_bloginfo( 'url' );
457
		}
458
459
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] )
460
			? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ]
461
			: false;
462
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] )
463
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ]
464
			: false;
465
466
		?>
467
		<div id="jetpack-sso-wrap">
468
			<?php if ( $display_name && $gravatar ) : ?>
469
				<div id="jetpack-sso-wrap__user">
470
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
471
472
					<h2>
473
						<?php
474
							echo wp_kses(
475
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
476
								array( 'span' => true )
477
							);
478
						?>
479
					</h2>
480
				</div>
481
482
			<?php endif; ?>
483
484
485
			<div id="jetpack-sso-wrap__action">
486
				<?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...
487
488
				<?php if ( $display_name && $gravatar ) : ?>
489
					<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
490
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
491
					</a>
492
				<?php else : ?>
493
					<p>
494
						<?php
495
							echo esc_html(
496
								sprintf(
497
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
498
									esc_html( $site_name )
499
								)
500
							);
501
						?>
502
					</p>
503
				<?php endif; ?>
504
			</div>
505
506
			<?php if ( ! Jetpack_SSO_Helpers::should_hide_login_form() ) : ?>
507
				<div class="jetpack-sso-or">
508
					<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
509
				</div>
510
511
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '1' ); ?>" class="jetpack-sso-toggle wpcom">
512
					<?php
513
						esc_html_e( 'Log in with username and password', 'jetpack' )
514
					?>
515
				</a>
516
517
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '0' ); ?>" class="jetpack-sso-toggle default">
518
					<?php
519
						esc_html_e( 'Log in with WordPress.com', 'jetpack' )
520
					?>
521
				</a>
522
			<?php endif; ?>
523
		</div>
524
		<?php
525
	}
526
527
	/**
528
	 * Clear the cookies that store the profile information for the last
529
	 * WPCOM user to connect.
530
	 */
531
	static function clear_wpcom_profile_cookies() {
532 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
533
			setcookie(
534
				'jetpack_sso_wpcom_name_' . COOKIEHASH,
535
				' ',
536
				time() - YEAR_IN_SECONDS,
537
				COOKIEPATH,
538
				COOKIE_DOMAIN
539
			);
540
		}
541
542 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
543
			setcookie(
544
				'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
545
				' ',
546
				time() - YEAR_IN_SECONDS,
547
				COOKIEPATH,
548
				COOKIE_DOMAIN
549
			);
550
		}
551
	}
552
553
	static function delete_connection_for_user( $user_id ) {
554
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
555
			return;
556
		}
557
		Jetpack::load_xml_rpc_client();
558
		$xml = new Jetpack_IXR_Client( array(
559
			'wpcom_user_id' => $user_id,
560
		) );
561
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
562
563
		if ( $xml->isError() ) {
564
			return false;
565
		}
566
567
		// Clean up local data stored for SSO
568
		delete_user_meta( $user_id, 'wpcom_user_id' );
569
		delete_user_meta( $user_id, 'wpcom_user_data'  );
570
		self::clear_wpcom_profile_cookies();
571
572
		return $xml->getResponse();
573
	}
574
575 View Code Duplication
	static function request_initial_nonce() {
576
		Jetpack::load_xml_rpc_client();
577
		$xml = new Jetpack_IXR_Client( array(
578
			'user_id' => get_current_user_id(),
579
		) );
580
		$xml->query( 'jetpack.sso.requestNonce' );
581
582
		if ( $xml->isError() ) {
583
			return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
584
		}
585
586
		return $xml->getResponse();
587
	}
588
589
	/**
590
	 * The function that actually handles the login!
591
	 */
592
	function handle_login() {
593
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
594
		$wpcom_user_id = (int) $_GET['user_id'];
595
596
		Jetpack::load_xml_rpc_client();
597
		$xml = new Jetpack_IXR_Client( array(
598
			'user_id' => get_current_user_id(),
599
		) );
600
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
601
602
		if ( $xml->isError() ) {
603
			$error_message = sanitize_text_field(
604
				sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() )
605
			);
606
			JetpackTracking::record_user_event( 'sso_login_failed', array(
607
				'error_message' => $error_message
608
			) );
609
			wp_die( $error_message );
610
		}
611
612
		$user_data = $xml->getResponse();
613
614
		if ( empty( $user_data ) ) {
615
			JetpackTracking::record_user_event( 'sso_login_failed', array(
616
				'error_message' => 'invalid_response_data'
617
			) );
618
			wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
619
		}
620
621
		$user_data = (object) $user_data;
622
		$user = null;
623
624
		/**
625
		 * Fires before Jetpack's SSO modifies the log in form.
626
		 *
627
		 * @module sso
628
		 *
629
		 * @since 2.6.0
630
		 *
631
		 * @param object $user_data User login information.
632
		 */
633
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
634
635
		if ( Jetpack_SSO_Helpers::is_two_step_required() && 0 === (int) $user_data->two_step_enabled ) {
636
			$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...
637
638
			JetpackTracking::record_user_event( 'sso_login_failed', array(
639
				'error_message' => 'error_msg_enable_two_step'
640
			) );
641
			
642
			/** This filter is documented in core/src/wp-includes/pluggable.php */
643
			do_action( 'wp_login_failed', $user_data->login );
644
			add_filter( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
645
			return;
646
		}
647
648
		$user_found_with = '';
649
		if ( empty( $user ) && isset( $user_data->external_user_id ) ) {
650
			$user_found_with = 'external_user_id';
651
			$user = get_user_by( 'id', intval( $user_data->external_user_id ) );
652
			if ( $user ) {
653
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
654
			}
655
		}
656
657
		// If we don't have one by wpcom_user_id, try by the email?
658
		if ( empty( $user ) && Jetpack_SSO_Helpers::match_by_email() ) {
659
			$user_found_with = 'match_by_email';
660
			$user = get_user_by( 'email', $user_data->email );
661
			if ( $user ) {
662
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
663
			}
664
		}
665
666
		// If we've still got nothing, create the user.
667
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || Jetpack_SSO_Helpers::new_user_override() ) ) {
668
			// If not matching by email we still need to verify the email does not exist
669
			// or this blows up
670
			/**
671
			 * If match_by_email is true, we know the email doesn't exist, as it would have
672
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
673
			 * user, then we know that email is unused, so it's safe to add.
674
			 */
675
			if ( Jetpack_SSO_Helpers::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
676
				$username = $user_data->login;
677
678
				if ( username_exists( $username ) ) {
679
					$username = $user_data->login . '_' . $user_data->ID;
680
				}
681
682
				$tries = 0;
683
				while ( username_exists( $username ) ) {
684
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
685
					if ( $tries++ >= 5 ) {
686
						JetpackTracking::record_user_event( 'sso_login_failed', array(
687
							'error_message' => 'could_not_create_username'
688
						) );
689
						wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
690
					}
691
				}
692
693
				$user_found_with = Jetpack_SSO_Helpers::new_user_override()
694
					? 'user_created_new_user_override'
695
					: 'user_created_users_can_register';
696
697
				$password = wp_generate_password( 20 );
698
				$user_id  = wp_create_user( $username, $password, $user_data->email );
699
				$user     = get_userdata( $user_id );
700
701
				$user->display_name = $user_data->display_name;
702
				$user->first_name   = $user_data->first_name;
703
				$user->last_name    = $user_data->last_name;
704
				$user->url          = $user_data->url;
705
				$user->description  = $user_data->description;
706
				wp_update_user( $user );
707
708
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
709
			} else {
710
				JetpackTracking::record_user_event( 'sso_login_failed', array(
711
					'error_message' => 'error_msg_email_already_exists'
712
				) );
713
714
				$this->user_data = $user_data;
715
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
716
				return;
717
			}
718
		}
719
720
		/**
721
		 * Fires after we got login information from WordPress.com.
722
		 *
723
		 * @module sso
724
		 *
725
		 * @since 2.6.0
726
		 *
727
		 * @param array $user WordPress.com User information.
728
		 * @param object $user_data User Login information.
729
		 */
730
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
731
732
		if ( $user ) {
733
			// Cache the user's details, so we can present it back to them on their user screen
734
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
735
736
			$remember = false;
737 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
738
				$remember = true;
739
				// And then purge it
740
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
741
			}
742
			/**
743
			 * Filter the remember me value.
744
			 *
745
			 * @module sso
746
			 *
747
			 * @since 2.8.0
748
			 *
749
			 * @param bool $remember Is the remember me option checked?
750
			 */
751
			$remember = apply_filters( 'jetpack_remember_login', $remember );
752
			wp_set_auth_cookie( $user->ID, $remember );
753
754
			/** This filter is documented in core/src/wp-includes/user.php */
755
			do_action( 'wp_login', $user->user_login, $user );
756
757
			wp_set_current_user( $user->ID );
758
759
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
760
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
761
762
			// If we have a saved redirect to request in a cookie
763 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
764
				// Set that as the requested redirect to
765
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
766
				// And then purge it
767
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
768
			}
769
770
			$is_user_connected = Jetpack::is_user_connected( $user->ID );
771
			JetpackTracking::record_user_event( 'sso_user_logged_in', array(
772
				'user_found_with' => $user_found_with,
773
				'user_connected'  => (bool) $is_user_connected,
774
				'user_role'       => Jetpack::init()->translate_current_user_to_role()
775
			) );
776
777
			if ( ! $is_user_connected ) {
778
				$calypso_env = ! empty( $_GET['calypso_env'] )
779
					? sanitize_key( $_GET['calypso_env'] )
780
					: '';
781
782
				wp_safe_redirect(
783
					add_query_arg(
784
						array(
785
							'redirect_to'               => $redirect_to,
786
							'request_redirect_to'       => $_request_redirect_to,
787
							'calypso_env'               => $calypso_env,
788
							'jetpack-sso-auth-redirect' => '1',
789
						),
790
						admin_url()
791
					)
792
				);
793
				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...
794
			}
795
796
			wp_safe_redirect(
797
				/** This filter is documented in core/src/wp-login.php */
798
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
799
			);
800
			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...
801
		}
802
803
		JetpackTracking::record_user_event( 'sso_login_failed', array(
804
			'error_message' => 'cant_find_user'
805
		) );
806
807
		$this->user_data = $user_data;
808
		/** This filter is documented in core/src/wp-includes/pluggable.php */
809
		do_action( 'wp_login_failed', $user_data->login );
810
		add_filter( 'login_message', array( $this, 'cant_find_user' ) );
811
	}
812
813
	static function profile_page_url() {
814
		return admin_url( 'profile.php' );
815
	}
816
817
	function allowed_redirect_hosts( $hosts ) {
818
		if ( empty( $hosts ) ) {
819
			$hosts = array();
820
		}
821
		$hosts[] = 'wordpress.com';
822
		$hosts[] = 'jetpack.wordpress.com';
823
824
		return array_unique( $hosts );
825
	}
826
827
	/**
828
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
829
	 *
830
	 * @param  array   $args       An array of arguments to add to the SSO URL.
831
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
832
	 * @return string              Returns the HTML markup for the button.
833
	 */
834
	function build_sso_button( $args = array(), $is_primary = false ) {
835
		$url = $this->build_sso_button_url( $args );
836
		$classes = $is_primary
837
			? 'jetpack-sso button button-primary'
838
			: 'jetpack-sso button';
839
840
		return sprintf(
841
			'<a rel="nofollow" href="%1$s" class="%2$s">%3$s</a>',
842
			esc_url( $url ),
843
			$classes,
844
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
845
		);
846
	}
847
848
	/**
849
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
850
	 *
851
	 * @param  array  $args An array of arguments to add to the SSO URL.
852
	 * @return string       The URL used for SSO.
853
	 */
854
	function build_sso_button_url( $args = array() ) {
855
		$defaults = array(
856
			'action'  => 'jetpack-sso',
857
		);
858
859
		$args = wp_parse_args( $args, $defaults );
860
861
		if ( ! empty( $_GET['redirect_to'] ) ) {
862
			$args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
863
		}
864
865
		return add_query_arg( $args, wp_login_url() );
866
	}
867
868
	/**
869
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
870
	 *
871
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
872
	 * @param  array    $args    Optional query parameters.
873
	 * @return string            The WordPress.com SSO URL.
874
	 */
875
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
876
		if ( empty( $reauth ) ) {
877
			$sso_redirect = $this->build_sso_url( $args );
878
		} else {
879
			self::clear_wpcom_profile_cookies();
880
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
881
		}
882
883
		// If there was an error retrieving the SSO URL, then error.
884
		if ( is_wp_error( $sso_redirect ) ) {
885
			$error_message = sanitize_text_field(
886
				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...
887
			);
888
			JetpackTracking::record_user_event( 'sso_login_redirect_failed', array(
889
				'error_message' => $error_message
890
			) );
891
			wp_die( $error_message );
892
		}
893
894
		return $sso_redirect;
895
	}
896
897
	/**
898
	 * Build WordPress.com SSO URL with appropriate query parameters.
899
	 *
900
	 * @param  array  $args Optional query parameters.
901
	 * @return string       WordPress.com SSO URL
902
	 */
903
	function build_sso_url( $args = array() ) {
904
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
905
		$defaults = array(
906
			'action'    => 'jetpack-sso',
907
			'site_id'   => Jetpack_Options::get_option( 'id' ),
908
			'sso_nonce' => $sso_nonce,
909
		);
910
911
		$args = wp_parse_args( $args, $defaults );
912
913
		if ( is_wp_error( $args['sso_nonce'] ) ) {
914
			return $args['sso_nonce'];
915
		}
916
917
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
918
	}
919
920
	/**
921
	 * Build WordPress.com SSO URL with appropriate query parameters,
922
	 * including the parameters necessary to force the user to reauthenticate
923
	 * on WordPress.com.
924
	 *
925
	 * @param  array  $args Optional query parameters.
926
	 * @return string       WordPress.com SSO URL
927
	 */
928
	function build_reauth_and_sso_url( $args = array() ) {
929
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
930
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
931
932
		if ( is_wp_error( $redirect ) ) {
933
			return $redirect;
934
		}
935
936
		$defaults = array(
937
			'action'      => 'jetpack-sso',
938
			'site_id'     => Jetpack_Options::get_option( 'id' ),
939
			'sso_nonce'   => $sso_nonce,
940
			'reauth'      => '1',
941
			'redirect_to' => urlencode( $redirect ),
942
		);
943
944
		$args = wp_parse_args( $args, $defaults );
945
946
		if ( is_wp_error( $args['sso_nonce'] ) ) {
947
			return $args['sso_nonce'];
948
		}
949
950
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
951
	}
952
953
	/**
954
	 * Determines local user associated with a given WordPress.com user ID.
955
	 *
956
	 * @since 2.6.0
957
	 *
958
	 * @param int $wpcom_user_id User ID from WordPress.com
959
	 * @return object Local user object if found, null if not.
960
	 */
961
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
962
		$user_query = new WP_User_Query( array(
963
			'meta_key'   => 'wpcom_user_id',
964
			'meta_value' => intval( $wpcom_user_id ),
965
			'number'     => 1,
966
		) );
967
968
		$users = $user_query->get_results();
969
		return $users ? array_shift( $users ) : null;
970
	}
971
972
	/**
973
	 * Error message displayed on the login form when two step is required and
974
	 * the user's account on WordPress.com does not have two step enabled.
975
	 *
976
	 * @since 2.7
977
	 * @param string $message
978
	 * @return string
979
	 **/
980
	public function error_msg_enable_two_step( $message ) {
981
		$error = sprintf(
982
			wp_kses(
983
				__(
984
					'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.',
985
					'jetpack'
986
				),
987
				array(  'a' => array( 'href' => array() ) )
988
			),
989
			'https://wordpress.com/me/security/two-step',
990
			'https://support.wordpress.com/security/two-step-authentication/'
991
		);
992
993
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
994
995
		return $message;
996
	}
997
998
	/**
999
	 * Error message displayed when the user tries to SSO, but match by email
1000
	 * is off and they already have an account with their email address on
1001
	 * this site.
1002
	 *
1003
	 * @param string $message
1004
	 * @return string
1005
	 */
1006
	public function error_msg_email_already_exists( $message ) {
1007
		$error = sprintf(
1008
			wp_kses(
1009
				__(
1010
					'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.',
1011
					'jetpack'
1012
				),
1013
				array(  'a' => array( 'href' => array() ) )
1014
			),
1015
			esc_url_raw( add_query_arg( 'jetpack-sso-default-form', '1', wp_login_url() ) )
1016
		);
1017
1018
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1019
1020
		return $message;
1021
	}
1022
1023
	/**
1024
	 * Builds the translation ready string that is to be used when the site hides the default login form.
1025
	 *
1026
	 * @since 4.1.0
1027
	 * 
1028
	 * @return string
1029
	 */
1030
	public function get_sso_required_message() {
1031
		$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' );
1032
1033
		/**
1034
		 * Filter the message displayed when the default WordPress login form is disabled.
1035
		 *
1036
		 * @module sso
1037
		 *
1038
		 * @since 2.8.0
1039
		 *
1040
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
1041
		 */
1042
		return apply_filters( 'jetpack_sso_disclaimer_message', $msg );
1043
	}
1044
1045
	/**
1046
	 * Message displayed when the site admin has disabled the default WordPress
1047
	 * login form in Settings > General > Single Sign On
1048
	 *
1049
	 * @since 2.7
1050
	 * @param string $message
1051
	 * 
1052
	 * @return string
1053
	 **/
1054
	public function msg_login_by_jetpack( $message ) {
1055
		$msg = $this->get_sso_required_message();
1056
1057
		if ( empty( $msg ) ) {
1058
			return $message;
1059
		}
1060
1061
		$message .= sprintf( '<p class="message">%s</p>', $msg );
1062
		return $message;
1063
	}
1064
1065
	/**
1066
	 * Message displayed when the user can not be found after approving the SSO process on WordPress.com
1067
	 *
1068
	 * @param string $message
1069
	 * @return string
1070
	 */
1071
	function cant_find_user( $message ) {
1072
		$error = esc_html__(
1073
			"We couldn't find your account. If you already have an account, make sure you have connected to WordPress.com.",
1074
			'jetpack'
1075
		);
1076
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $error );
1077
1078
		return $message;
1079
	}
1080
1081
	/**
1082
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
1083
	 * WordPress.com authorization flow.
1084
	 *
1085
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1086
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1087
	 */
1088
	function maybe_authorize_user_after_sso() {
1089
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1090
			return;
1091
		}
1092
1093
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url();
1094
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to;
1095
1096
		/** This filter is documented in core/src/wp-login.php */
1097
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
1098
1099
		/**
1100
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1101
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1102
		 */
1103
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1104
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1105
1106
		/**
1107
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1108
		 */
1109
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1110
1111
		add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
1112
		wp_safe_redirect( $connect_url );
1113
		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...
1114
	}
1115
1116
	/**
1117
	 * Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are
1118
	 * stored when the user logs out, and then deleted when the user logs in.
1119
	 */
1120
	function store_wpcom_profile_cookies_on_logout() {
1121
		if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) {
1122
			return;
1123
		}
1124
1125
		$user_data = $this->get_user_data( get_current_user_id() );
1126
		if ( ! $user_data ) {
1127
			return;
1128
		}
1129
1130
		setcookie(
1131
			'jetpack_sso_wpcom_name_' . COOKIEHASH,
1132
			$user_data->display_name,
1133
			time() + WEEK_IN_SECONDS,
1134
			COOKIEPATH,
1135
			COOKIE_DOMAIN
1136
		);
1137
1138
		setcookie(
1139
			'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
1140
			get_avatar_url(
1141
				$user_data->email,
1142
				array( 'size' => 72, 'default' => 'mystery' )
1143
			),
1144
			time() + WEEK_IN_SECONDS,
1145
			COOKIEPATH,
1146
			COOKIE_DOMAIN
1147
		);
1148
	}
1149
1150
	/**
1151
	 * Determines if a local user is connected to WordPress.com
1152
	 *
1153
	 * @since 2.8
1154
	 * @param integer $user_id - Local user id
1155
	 * @return boolean
1156
	 **/
1157
	public function is_user_connected( $user_id ) {
1158
		return $this->get_user_data( $user_id );
1159
	}
1160
1161
	/**
1162
	 * Retrieves a user's WordPress.com data
1163
	 *
1164
	 * @since 2.8
1165
	 * @param integer $user_id - Local user id
1166
	 * @return mixed null or stdClass
1167
	 **/
1168
	public function get_user_data( $user_id ) {
1169
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1170
	}
1171
}
1172
1173
Jetpack_SSO::get_instance();
1174