Completed
Push — feature/videopress-uploader ( c50b88...58b703 )
by
unknown
41:05 queued 31:04
created

Jetpack_SSO   D

Complexity

Total Complexity 155

Size/Duplication

Total Lines 1250
Duplicated Lines 3.36 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 4
Bugs 0 Features 2
Metric Value
wmc 155
c 4
b 0
f 2
lcom 2
cbo 3
dl 42
loc 1250
rs 4.4102

51 Methods

Rating   Name   Duplication   Size   Complexity  
A profile_page_url() 0 3 1
A match_by_email() 0 15 3
A new_user_override() 0 14 2
A module_configure_button() 0 6 1
A module_configuration_load() 0 4 1
A module_configuration_head() 0 1 1
A module_configuration_screen() 0 9 1
A xmlrpc_methods() 0 4 1
B __construct() 0 36 3
A get_instance() 0 7 2
A maybe_logout_user() 0 11 2
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
A register_settings() 0 64 1
A render_require_two_step() 0 9 2
A validate_jetpack_sso_require_two_step() 0 3 2
A render_match_by_email() 0 8 2
A validate_jetpack_sso_match_by_email() 0 3 2
A render_remove_login_form_checkbox() 0 10 2
A validate_settings_remove_login_form_checkbox() 0 3 2
A wants_to_login() 0 15 4
A bypass_login_forward_wpcom() 0 12 1
C login_init() 0 48 10
A display_sso_login_form() 0 10 2
B maybe_save_cookie_redirect() 0 21 6
A should_hide_login_form() 0 12 1
A request_initial_nonce() 13 13 2
F handle_login() 11 202 28
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 15 3
B build_sso_url() 0 20 5
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 7 1
A error_msg_email_already_exists() 0 7 1
A msg_login_by_jetpack() 0 18 1
A error_msg_login_method_not_allowed() 0 6 1
A cant_find_user() 0 10 2
B maybe_authorize_user_after_sso() 0 27 4
C login_form() 0 73 9
A clear_wpcom_profile_cookies() 18 21 3
B store_wpcom_profile_cookies_on_logout() 0 29 3
A admin_init() 0 16 4
A is_user_connected() 0 3 1
A get_user_data() 0 3 1
B edit_profile_fields() 0 43 5
A delete_connection_for_user() 0 18 3

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

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...
334
		}
335
	}
336
337
	/**
338
	 * Validate settings input from Settings > General
339
	 *
340
	 * @since 2.7
341
	 * @return boolean
342
	 **/
343
	public function validate_settings_remove_login_form_checkbox( $input ) {
344
		return ( isset( $input['remove_login_form'] ) )? 1: 0;
345
	}
346
347
	/**
348
	 * Checks to determine if the user wants to login on wp-login
349
	 *
350
	 * This function mostly exists to cover the exceptions to login
351
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
352
	 * does not have to exist. By default WordPress assumes login if an action
353
	 * is not set, however this may not be true, as in the case of logout
354
	 * where $_GET[loggedout] is instead set
355
	 *
356
	 * @return boolean
357
	 **/
358
	private function wants_to_login() {
359
		$wants_to_login = false;
360
361
		// Cover default WordPress behavior
362
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
363
364
		// And now the exceptions
365
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
366
367
		if ( 'login' == $action ) {
368
			$wants_to_login = true;
369
		}
370
371
		return $wants_to_login;
372
	}
373
374
	private function bypass_login_forward_wpcom() {
375
		/**
376
		 * Redirect the site's log in form to WordPress.com's log in form.
377
		 *
378
		 * @module sso
379
		 *
380
		 * @since 3.1.0
381
		 *
382
		 * @param bool false Should the site's log in form be automatically forwarded to WordPress.com's log in form.
383
		 */
384
		return apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
385
	}
386
387
	function login_init() {
388
		global $action;
389
390
		/**
391
		 * If the user is attempting to logout AND the auto-forward to WordPress.com
392
		 * login is set then we need to ensure we do not auto-forward the user and get
393
		 * them stuck in an infinite logout loop.
394
		 */
395
		if ( isset( $_GET['loggedout'] ) && $this->bypass_login_forward_wpcom() ) {
396
			add_filter( 'jetpack_remove_login_form', '__return_true' );
397
		}
398
399
		/**
400
		 * Check to see if the site admin wants to automagically forward the user
401
		 * to the WordPress.com login page AND  that the request to wp-login.php
402
		 * is not something other than login (Like logout!)
403
		 */
404
		if (
405
			$this->wants_to_login()
406
			&& $this->bypass_login_forward_wpcom()
407
		) {
408
			add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
409
			$this->maybe_save_cookie_redirect();
410
			$reauth = ! empty( $_GET['reauth'] );
411
			wp_safe_redirect( $this->get_sso_url_or_die( $reauth ) );
412
			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...
413
		}
414
415
		if ( 'login' === $action ) {
416
			$this->display_sso_login_form();
417
		} elseif ( 'jetpack-sso' === $action ) {
418
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
419
				$this->handle_login();
420
				$this->display_sso_login_form();
421
			} else {
422
				if ( Jetpack::check_identity_crisis() ) {
423
					wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
424
				} else {
425
					$this->maybe_save_cookie_redirect();
426
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
427
					add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
428
					$reauth = ! empty( $_GET['reauth'] );
429
					wp_safe_redirect( $this->get_sso_url_or_die( $reauth ) );
430
					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...
431
				}
432
			}
433
		}
434
	}
435
436
	/**
437
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
438
	 * up the hooks required to display the SSO form.
439
	 */
440
	public function display_sso_login_form() {
441
		$sso_nonce = self::request_initial_nonce();
442
		if ( is_wp_error( $sso_nonce ) ) {
443
			return;
444
		}
445
446
		add_action( 'login_form',            array( $this, 'login_form' ) );
447
		add_filter( 'login_body_class',      array( $this, 'login_body_class' ) );
448
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
449
	}
450
451
	/**
452
	 * Conditionally save the redirect_to url as a cookie.
453
	 */
454
	public static function maybe_save_cookie_redirect() {
455
		if ( headers_sent() ) {
456
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
457
		}
458
459
		if ( ! empty( $_GET['redirect_to'] ) ) {
460
			// If we have something to redirect to
461
			$url = esc_url_raw( $_GET['redirect_to'] );
462
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
463
464
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
465
			// Otherwise, if it's already set, purge it.
466
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
467
		}
468
469
		if ( ! empty( $_GET['rememberme'] ) ) {
470
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
471
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
472
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
473
		}
474
	}
475
476
	/**
477
	 * Determine if the login form should be hidden or not
478
	 *
479
	 * Method is private only because it is only used in this class so far.
480
	 * Feel free to change it later
481
	 *
482
	 * @return bool
483
	 **/
484
	private function should_hide_login_form() {
485
		/**
486
		 * Remove the default log in form, only leave the WordPress.com log in button.
487
		 *
488
		 * @module sso
489
		 *
490
		 * @since 3.1.0
491
		 *
492
		 * @param bool get_option( 'jetpack_sso_remove_login_form', false ) Should the default log in form be removed. Default to false.
493
		 */
494
		return apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
495
	}
496
497
	/**
498
	 * Outputs the Jetpack SSO button and description as well as the toggle link
499
	 * for switching between Jetpack SSO and default login.
500
	 */
501
	function login_form() {
502
		$site_name = get_bloginfo( 'name' );
503
		if ( ! $site_name ) {
504
			$site_name = get_bloginfo( 'url' );
505
		}
506
507
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] )
508
			? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ]
509
			: false;
510
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] )
511
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ]
512
			: false;
513
514
		?>
515
		<div id="jetpack-sso-wrap">
516
			<?php if ( $display_name && $gravatar ) : ?>
517
				<div id="jetpack-sso-wrap__user">
518
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
519
520
					<h2>
521
						<?php
522
							echo wp_kses(
523
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
524
								array( 'span' => true )
525
							);
526
						?>
527
					</h2>
528
				</div>
529
530
			<?php endif; ?>
531
532
533
			<div id="jetpack-sso-wrap__action">
534
				<?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...
535
536
				<?php if ( $display_name && $gravatar ) : ?>
537
					<a class="jetpack-sso-wrap__reauth" href="<?php echo $this->build_sso_button_url( array( 'reauth' => '1' ) ); ?>">
538
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
539
					</a>
540
				<?php else : ?>
541
					<p>
542
						<?php
543
							echo esc_html(
544
								sprintf(
545
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
546
									esc_html( $site_name )
547
								)
548
							);
549
						?>
550
					</p>
551
				<?php endif; ?>
552
			</div>
553
554
			<?php if ( ! $this->should_hide_login_form() ) : ?>
555
				<div class="jetpack-sso-or">
556
					<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
557
				</div>
558
559
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '1' ); ?>" class="jetpack-sso-toggle wpcom">
560
					<?php
561
						esc_html_e( 'Log in with username and password', 'jetpack' )
562
					?>
563
				</a>
564
565
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '0' ); ?>" class="jetpack-sso-toggle default">
566
					<?php
567
						esc_html_e( 'Log in with WordPress.com', 'jetpack' )
568
					?>
569
				</a>
570
			<?php endif; ?>
571
		</div>
572
		<?php
573
	}
574
575
	/**
576
	 * Clear the cookies that store the profile information for the last
577
	 * WPCOM user to connect.
578
	 */
579
	static function clear_wpcom_profile_cookies() {
580 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
581
			setcookie(
582
				'jetpack_sso_wpcom_name_' . COOKIEHASH,
583
				' ',
584
				time() - YEAR_IN_SECONDS,
585
				COOKIEPATH,
586
				COOKIE_DOMAIN
587
			);
588
		}
589
590 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
591
			setcookie(
592
				'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
593
				' ',
594
				time() - YEAR_IN_SECONDS,
595
				COOKIEPATH,
596
				COOKIE_DOMAIN
597
			);
598
		}
599
	}
600
601
	static function delete_connection_for_user( $user_id ) {
602
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
603
			return;
604
		}
605
		Jetpack::load_xml_rpc_client();
606
		$xml = new Jetpack_IXR_Client( array(
607
			'wpcom_user_id' => $user_id,
608
		) );
609
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
610
611
		if ( $xml->isError() ) {
612
			return false;
613
		}
614
615
		self::clear_wpcom_profile_cookies();
616
617
		return $xml->getResponse();
618
	}
619
620 View Code Duplication
	static function request_initial_nonce() {
621
		Jetpack::load_xml_rpc_client();
622
		$xml = new Jetpack_IXR_Client( array(
623
			'user_id' => get_current_user_id(),
624
		) );
625
		$xml->query( 'jetpack.sso.requestNonce' );
626
627
		if ( $xml->isError() ) {
628
			return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
629
		}
630
631
		return $xml->getResponse();
632
	}
633
634
	/**
635
	 * The function that actually handles the login!
636
	 */
637
	function handle_login() {
638
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
639
		$wpcom_user_id = (int) $_GET['user_id'];
640
		$result        = sanitize_key( $_GET['result'] );
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

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

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

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

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

Loading history...
641
642
		Jetpack::load_xml_rpc_client();
643
		$xml = new Jetpack_IXR_Client( array(
644
			'user_id' => get_current_user_id(),
645
		) );
646
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
647
648
		if ( $xml->isError() ) {
649
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
650
		}
651
652
		$user_data = $xml->getResponse();
653
654
		if ( empty( $user_data ) ) {
655
			wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
656
		}
657
658
		$user_data = (object) $user_data;
659
		$user = null;
660
661
		/**
662
		 * Fires before Jetpack's SSO modifies the log in form.
663
		 *
664
		 * @module sso
665
		 *
666
		 * @since 2.6.0
667
		 *
668
		 * @param object $user_data User login information.
669
		 */
670
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
671
672
		/**
673
		 * Is it required to have 2-step authentication enabled on WordPress.com to use SSO?
674
		 *
675
		 * @module sso
676
		 *
677
		 * @since 2.8.0
678
		 *
679
		 * @param bool get_option( 'jetpack_sso_require_two_step' ) Does SSO require 2-step authentication?
680
		 */
681
		$require_two_step = apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) );
682
		if ( $require_two_step && 0 == (int) $user_data->two_step_enabled ) {
683
			$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...
684
			/** This filter is documented in core/src/wp-includes/pluggable.php */
685
			do_action( 'wp_login_failed', $user_data->login );
686
			add_action( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
687
			return;
688
		}
689
690
		if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
691
			list( $state, $nonce ) = explode( '|', $_GET['state'] );
692
693
			if ( wp_verify_nonce( $nonce, $state ) ) {
694
				if ( 'sso-link-user' == $state ) {
695
					$user = wp_get_current_user();
696
					update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
697
					add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
698
				}
699
			} else {
700
				wp_nonce_ays();
701
			}
702
		}
703
704
		if ( empty( $user ) ) {
705
			$user = $this->get_user_by_wpcom_id( $user_data->ID );
706
		}
707
708
		// If we don't have one by wpcom_user_id, try by the email?
709
		if ( empty( $user ) && self::match_by_email() ) {
710
			$user = get_user_by( 'email', $user_data->email );
711
			if ( $user ) {
712
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
713
			}
714
		}
715
716
		// If we've still got nothing, create the user.
717
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
718
			// If not matching by email we still need to verify the email does not exist
719
			// or this blows up
720
			/**
721
			 * If match_by_email is true, we know the email doesn't exist, as it would have
722
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
723
			 * user, then we know that email is unused, so it's safe to add.
724
			 */
725
			if ( self::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
726
				$username = $user_data->login;
727
728
				if ( username_exists( $username ) ) {
729
					$username = $user_data->login . '_' . $user_data->ID;
730
				}
731
732
				$tries = 0;
733
				while ( username_exists( $username ) ) {
734
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
735
					if ( $tries++ >= 5 ) {
736
						wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
737
					}
738
				}
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
				$this->user_data = $user_data;
754
				// do_action( 'wp_login_failed', $user_data->login );
755
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
756
				return;
757
			}
758
		}
759
760
		/**
761
		 * Fires after we got login information from WordPress.com.
762
		 *
763
		 * @module sso
764
		 *
765
		 * @since 2.6.0
766
		 *
767
		 * @param array $user WordPress.com User information.
768
		 * @param object $user_data User Login information.
769
		 */
770
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
771
772
		if ( $user ) {
773
			// Cache the user's details, so we can present it back to them on their user screen
774
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
775
776
			$remember = false;
777 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
778
				$remember = true;
779
				// And then purge it
780
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
781
			}
782
			/**
783
			 * Filter the remember me value.
784
			 *
785
			 * @module sso
786
			 *
787
			 * @since 2.8.0
788
			 *
789
			 * @param bool $remember Is the remember me option checked?
790
			 */
791
			$remember = apply_filters( 'jetpack_remember_login', $remember );
792
			wp_set_auth_cookie( $user->ID, $remember );
793
794
			/** This filter is documented in core/src/wp-includes/user.php */
795
			do_action( 'wp_login', $user->user_login, $user );
796
797
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
798
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
799
800
			// If we have a saved redirect to request in a cookie
801 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
802
				// Set that as the requested redirect to
803
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
804
				// And then purge it
805
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
806
			}
807
808
			if ( ! Jetpack::is_user_connected( $user->ID ) ) {
809
				$calypso_env = ! empty( $_GET['calypso_env'] )
810
					? sanitize_key( $_GET['calypso_env'] )
811
					: '';
812
813
				wp_safe_redirect(
814
					add_query_arg(
815
						array(
816
							'redirect_to'               => $redirect_to,
817
							'request_redirect_to'       => $_request_redirect_to,
818
							'calypso_env'               => $calypso_env,
819
							'jetpack-sso-auth-redirect' => '1',
820
						),
821
						admin_url()
822
					)
823
				);
824
				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...
825
			}
826
827
			wp_safe_redirect(
828
				/** This filter is documented in core/src/wp-login.php */
829
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
830
			);
831
			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...
832
		}
833
834
		$this->user_data = $user_data;
835
		/** This filter is documented in core/src/wp-includes/pluggable.php */
836
		do_action( 'wp_login_failed', $user_data->login );
837
		add_action( 'login_message', array( $this, 'cant_find_user' ) );
838
	}
839
840
	static function profile_page_url() {
841
		return admin_url( 'profile.php' );
842
	}
843
844
	static function match_by_email() {
845
		$match_by_email = ( 1 == get_option( 'jetpack_sso_match_by_email', true ) ) ? true: false;
846
		$match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : $match_by_email;
847
848
		/**
849
		 * Link the local account to an account on WordPress.com using the same email address.
850
		 *
851
		 * @module sso
852
		 *
853
		 * @since 2.6.0
854
		 *
855
		 * @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.
856
		 */
857
		return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
858
	}
859
860
	static function new_user_override() {
861
		$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
862
863
		/**
864
		 * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
865
		 *
866
		 * @module sso
867
		 *
868
		 * @since 2.6.0
869
		 *
870
		 * @param bool $new_user_override Allow users to register on your site with a WordPress.com account. Default to false.
871
		 */
872
		return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
873
	}
874
875
	function allowed_redirect_hosts( $hosts ) {
876
		if ( empty( $hosts ) ) {
877
			$hosts = array();
878
		}
879
		$hosts[] = 'wordpress.com';
880
		$hosts[] = 'jetpack.wordpress.com';
881
882
		return array_unique( $hosts );
883
	}
884
885
	/**
886
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
887
	 *
888
	 * @param  array   $args       An array of arguments to add to the SSO URL.
889
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
890
	 * @return string              Returns the HTML markup for the button.
891
	 */
892
	function build_sso_button( $args = array(), $is_primary = false ) {
893
		$url = $this->build_sso_button_url( $args );
894
		$classes = $is_primary
895
			? 'jetpack-sso button button-primary'
896
			: 'jetpack-sso button';
897
898
		return sprintf(
899
			'<a href="%1$s" class="%2$s">%3$s</a>',
900
			esc_url( $url ),
901
			$classes,
902
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
903
		);
904
	}
905
906
	/**
907
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
908
	 *
909
	 * @param  array  $args An array of arguments to add to the SSO URL.
910
	 * @return string       The URL used for SSO.
911
	 */
912
	function build_sso_button_url( $args = array() ) {
913
		$defaults = array(
914
			'action'  => 'jetpack-sso',
915
		);
916
917
		$args = wp_parse_args( $args, $defaults );
918
919
		if ( ! empty( $_GET['redirect_to'] ) ) {
920
			$args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
921
		}
922
923
		return add_query_arg( $args, wp_login_url() );
924
	}
925
926
	/**
927
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
928
	 *
929
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
930
	 * @param  array    $args    Optional query parameters.
931
	 * @return string            The WordPress.com SSO URL.
932
	 */
933
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
934
		if ( empty( $reauth ) ) {
935
			$sso_redirect = $this->build_sso_url( $args );
936
		} else {
937
			self::clear_wpcom_profile_cookies();
938
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
939
		}
940
941
		// If there was an error retrieving the SSO URL, then error.
942
		if ( is_wp_error( $sso_redirect ) ) {
943
			wp_die( 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...
944
		}
945
946
		return $sso_redirect;
947
	}
948
949
	/**
950
	 * Build WordPress.com SSO URL with appropriate query parameters.
951
	 *
952
	 * @param  array  $args Optional query parameters.
953
	 * @return string       WordPress.com SSO URL
954
	 */
955
	function build_sso_url( $args = array() ) {
956
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
957
		$defaults = array(
958
			'action'    => 'jetpack-sso',
959
			'site_id'   => Jetpack_Options::get_option( 'id' ),
960
			'sso_nonce' => $sso_nonce,
961
		);
962
963
		if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
964
			$defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
965
		}
966
967
		$args = wp_parse_args( $args, $defaults );
968
969
		if ( is_wp_error( $args['sso_nonce'] ) ) {
970
			return $args['sso_nonce'];
971
		}
972
973
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
974
	}
975
976
	/**
977
	 * Build WordPress.com SSO URL with appropriate query parameters,
978
	 * including the parameters necessary to force the user to reauthenticate
979
	 * on WordPress.com.
980
	 *
981
	 * @param  array  $args Optional query parameters.
982
	 * @return string       WordPress.com SSO URL
983
	 */
984
	function build_reauth_and_sso_url( $args = array() ) {
985
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
986
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
987
988
		if ( is_wp_error( $redirect ) ) {
989
			return $redirect;
990
		}
991
992
		$defaults = array(
993
			'action'      => 'jetpack-sso',
994
			'site_id'     => Jetpack_Options::get_option( 'id' ),
995
			'sso_nonce'   => $sso_nonce,
996
			'reauth'      => '1',
997
			'redirect_to' => urlencode( $redirect ),
998
		);
999
1000
		$args = wp_parse_args( $args, $defaults );
1001
1002
		if ( is_wp_error( $args['sso_nonce'] ) ) {
1003
			return $args['sso_nonce'];
1004
		}
1005
1006
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
1007
	}
1008
1009
	/**
1010
	 * Determines local user associated with a given WordPress.com user ID.
1011
	 *
1012
	 * @since 2.6.0
1013
	 *
1014
	 * @param int $wpcom_user_id User ID from WordPress.com
1015
	 * @return object Local user object if found, null if not.
1016
	 */
1017
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
1018
		$user_query = new WP_User_Query( array(
1019
			'meta_key'   => 'wpcom_user_id',
1020
			'meta_value' => intval( $wpcom_user_id ),
1021
			'number'     => 1,
1022
		) );
1023
1024
		$users = $user_query->get_results();
1025
		return $users ? array_shift( $users ) : null;
1026
	}
1027
1028
	/**
1029
	 * Error message displayed on the login form when two step is required and
1030
	 * the user's account on WordPress.com does not have two step enabled.
1031
	 *
1032
	 * @since 2.7
1033
	 * @param string $message
1034
	 * @return string
1035
	 **/
1036
	public function error_msg_enable_two_step( $message ) {
1037
		$err = __( sprintf( 'This site requires two step authentication be enabled for your user account on WordPress.com. Please visit the <a href="%1$s" target="_blank"> Security Settings</a> page to enable two step', 'https://wordpress.com/me/security/two-step' ) , 'jetpack' );
1038
1039
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1040
1041
		return $message;
1042
	}
1043
1044
	/**
1045
	 * Error message displayed when the user tries to SSO, but match by email
1046
	 * is off and they already have an account with their email address on
1047
	 * this site.
1048
	 *
1049
	 * @param string $message
1050
	 * @return string
1051
	 */
1052
	public function error_msg_email_already_exists( $message ) {
1053
		$err = __( sprintf( 'You already have an account on this site. Please visit your <a href="%1$s">profile page</a> page to link your account to WordPress.com!', admin_url( 'profile.php' ) ) , 'jetpack' );
1054
1055
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1056
1057
		return $message;
1058
	}
1059
1060
	/**
1061
	 * Message displayed when the site admin has disabled the default WordPress
1062
	 * login form in Settings > General > Single Sign On
1063
	 *
1064
	 * @since 2.7
1065
	 * @param string $message
1066
	 * @return string
1067
	 **/
1068
	public function msg_login_by_jetpack( $message ) {
1069
1070
		$msg = __( sprintf( 'Jetpack authenticates through WordPress.com — to log in, enter your WordPress.com username and password, or <a href="%1$s" target="_blank">visit WordPress.com</a> to create a free account now.', 'http://wordpress.com/signup' ) , 'jetpack' );
1071
1072
		/**
1073
		 * Filter the message displayed when the default WordPress login form is disabled.
1074
		 *
1075
		 * @module sso
1076
		 *
1077
		 * @since 2.8.0
1078
		 *
1079
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
1080
		 */
1081
		$msg = apply_filters( 'jetpack_sso_disclaimer_message', $msg );
1082
1083
		$message .= sprintf( '<p class="message">%s</p>', $msg );
1084
		return $message;
1085
	}
1086
1087
	/**
1088
	 * Error message displayed on the login form when the user attempts
1089
	 * to post to the login form and it is disabled.
1090
	 *
1091
	 * @since 2.8
1092
	 * @param string $message
1093
	 * @param string
1094
	 **/
1095
	public function error_msg_login_method_not_allowed( $message ) {
1096
		$err = __( 'Login method not allowed' , 'jetpack' );
1097
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1098
1099
		return $message;
1100
	}
1101
	function cant_find_user( $message ) {
1102
		if ( self::match_by_email() ) {
1103
			$err_format = __( 'We couldn\'t find an account with the email <strong><code>%1$s</code></strong> to log you in with.  If you already have an account on <strong>%2$s</strong>, please make sure that <strong><code>%1$s</code></strong> is configured as the email address, or that you have connected to WordPress.com on your profile page.', 'jetpack' );
1104
		} else {
1105
			$err_format = __( 'We couldn\'t find any account on <strong>%2$s</strong> that is linked to your WordPress.com account to log you in with.  If you already have an account on <strong>%2$s</strong>, please make sure that you have connected to WordPress.com on your profile page.', 'jetpack' );
1106
		}
1107
		$err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
1108
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1109
		return $message;
1110
	}
1111
1112
	/**
1113
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
1114
	 * WordPress.com authorization flow.
1115
	 *
1116
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1117
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1118
	 */
1119
	function maybe_authorize_user_after_sso() {
1120
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1121
			return;
1122
		}
1123
1124
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url();
1125
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to;
1126
1127
		/** This filter is documented in core/src/wp-login.php */
1128
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
1129
1130
		/**
1131
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1132
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1133
		 */
1134
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1135
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1136
1137
		/**
1138
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1139
		 */
1140
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1141
1142
		add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
1143
		wp_safe_redirect( $connect_url );
1144
		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...
1145
	}
1146
1147
	/**
1148
	 * Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are
1149
	 * stored when the user logs out, and then deleted when the user logs in.
1150
	 */
1151
	function store_wpcom_profile_cookies_on_logout() {
1152
		if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) {
1153
			return;
1154
		}
1155
1156
		$user_data = $this->get_user_data( get_current_user_id() );
1157
		if ( ! $user_data ) {
1158
			return;
1159
		}
1160
1161
		setcookie(
1162
			'jetpack_sso_wpcom_name_' . COOKIEHASH,
1163
			$user_data->display_name,
1164
			time() + WEEK_IN_SECONDS,
1165
			COOKIEPATH,
1166
			COOKIE_DOMAIN
1167
		);
1168
1169
		setcookie(
1170
			'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
1171
			get_avatar_url(
1172
				$user_data->email,
1173
				array( 'size' => 72, 'default' => 'mystery' )
1174
			),
1175
			time() + WEEK_IN_SECONDS,
1176
			COOKIEPATH,
1177
			COOKIE_DOMAIN
1178
		);
1179
	}
1180
1181
	/**
1182
	 * Deal with user connections...
1183
	 */
1184
	function admin_init() {
1185
		add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
1186
		add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
1187
1188
		if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
1189
			$user = wp_get_current_user();
1190
			// Remove the connection on the wpcom end.
1191
			self::delete_connection_for_user( $user->ID );
1192
			// Clear it locally.
1193
			delete_user_meta( $user->ID, 'wpcom_user_id' );
1194
			delete_user_meta( $user->ID, 'wpcom_user_data' );
1195
			// Forward back to the profile page.
1196
			wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
1197
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method admin_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...
1198
		}
1199
	}
1200
1201
	/**
1202
	 * Determines if a local user is connected to WordPress.com
1203
	 *
1204
	 * @since 2.8
1205
	 * @param integer $user_id - Local user id
1206
	 * @return boolean
1207
	 **/
1208
	public function is_user_connected( $user_id ) {
1209
		return $this->get_user_data( $user_id );
1210
	}
1211
1212
	/**
1213
	 * Retrieves a user's WordPress.com data
1214
	 *
1215
	 * @since 2.8
1216
	 * @param integer $user_id - Local user id
1217
	 * @return mixed null or stdClass
1218
	 **/
1219
	public function get_user_data( $user_id ) {
1220
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1221
	}
1222
1223
	function edit_profile_fields( $user ) {
1224
		?>
1225
1226
		<h3 id="single-sign-on"><?php _e( 'Single Sign On', 'jetpack' ); ?></h3>
1227
		<p><?php _e( 'Connecting with Single Sign On enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
1228
1229
		<?php if ( $this->is_user_connected( $user->ID ) ) : /* If the user is currently connected... */ ?>
1230
			<?php $user_data = $this->get_user_data( $user->ID ); ?>
1231
			<table class="form-table jetpack-sso-form-table">
1232
				<tbody>
1233
					<tr>
1234
						<td>
1235
							<div class="profile-card">
1236
								<?php echo get_avatar( $user_data->email ); ?>
1237
								<p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
1238
								<p><?php echo esc_html( $user_data->login ); ?></p>
1239
								<span class="two_step">
1240
									<?php
1241
										if ( $user_data->two_step_enabled ) {
1242
											?> <p class="enabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Enabled', 'jetpack' ); ?></a></p> <?php
1243
										} else {
1244
											?> <p class="disabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Disabled', 'jetpack' ); ?></a></p> <?php
1245
										}
1246
									?>
1247
								</span>
1248
1249
							</div>
1250
							<p><a class="button button-secondary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( 'jetpack_sso', 'purge' ), 'jetpack_sso_purge' ) ); ?>"><?php _e( 'Unlink This Account', 'jetpack' ); ?></a></p>
1251
						</td>
1252
					</tr>
1253
				</tbody>
1254
			</table>
1255
		<?php elseif ( get_current_user_id() == $user->ID && Jetpack::is_user_connected( $user->ID ) ) : ?>
1256
1257
			<?php echo $this->build_sso_button( 'state=sso-link-user&_wpnonce=' . wp_create_nonce( 'sso-link-user' ) ); ?>
0 ignored issues
show
Documentation introduced by
'state=sso-link-user&_wp..._nonce('sso-link-user') is of type string, but the function expects a array.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1258
1259
		<?php else : ?>
1260
1261
			<p><?php esc_html_e( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?></p>
1262
			<a href="<?php echo Jetpack::init()->build_connect_url( false, get_edit_profile_url( get_current_user_id() ) . '#single-sign-on' ); ?>" class="button button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a>
1263
1264
		<?php endif;
1265
	}
1266
}
1267
1268
Jetpack_SSO::get_instance();
1269