Completed
Push — fix/twitter-cards-3778 ( 2ceea6...e05f35 )
by Jeremy
09:08
created

Jetpack_SSO::__construct()   B

Complexity

Conditions 3
Paths 2

Size

Total Lines 33
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 3
eloc 16
c 5
b 0
f 0
nc 2
nop 0
dl 0
loc 33
rs 8.8571
1
<?php
2
3
/**
4
 * Module Name: Single Sign On
5
 * Module Description: Secure user authentication.
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: Jumpstart, Performance-Security
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
34
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
35
		add_action( 'login_form_jetpack-sso', '__return_true' );
36
37
		if (
38
			$this->should_hide_login_form() &&
39
			/**
40
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
41
			 *
42
			 * @module sso
43
			 *
44
			 * @since 2.8.0
45
			 *
46
			 * @param bool true Should the disclaimer be displayed. Default to true.
47
			 */
48
			apply_filters( 'jetpack_sso_display_disclaimer', true )
49
		) {
50
			add_action( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
51
		}
52
	}
53
54
	/**
55
	 * Returns the single instance of the Jetpack_SSO object
56
	 *
57
	 * @since 2.8
58
	 * @return Jetpack_SSO
59
	 **/
60
	public static function get_instance() {
61
		if ( ! is_null( self::$instance ) ) {
62
			return self::$instance;
63
		}
64
65
		return self::$instance = new Jetpack_SSO;
66
	}
67
68
	/**
69
	 * Add configure button and functionality to the module card on the Jetpack screen
70
	 **/
71
	public static function module_configure_button() {
72
		Jetpack::enable_module_configurable( __FILE__ );
73
		Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
74
		Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
75
		Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
76
	}
77
78
	public static function module_configuration_load() {
79
		// wp_safe_redirect( admin_url( 'options-general.php#configure-sso' ) );
80
		// exit;
81
	}
82
83
	public static function module_configuration_head() {}
84
85
	public static function module_configuration_screen() {
86
		?>
87
		<form method="post" action="options.php">
88
			<?php settings_fields( 'jetpack-sso' ); ?>
89
			<?php do_settings_sections( 'jetpack-sso' ); ?>
90
			<?php submit_button(); ?>
91
		</form>
92
		<?php
93
	}
94
95
	/**
96
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
97
	 * to logout and reauthenticate with the site.
98
	 **/
99
	public function maybe_logout_user() {
100
		global $current_user;
101
102
		if ( 1 == $current_user->jetpack_force_logout ) {
103
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
104
			self::delete_connection_for_user( $current_user->ID );
105
			wp_logout();
106
			wp_safe_redirect( wp_login_url() );
107
			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...
108
		}
109
	}
110
111
112
	/**
113
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
114
	 *
115
	 * @param array $methods
116
	 * @return array
117
	 **/
118
	public function xmlrpc_methods( $methods ) {
119
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
120
		return $methods;
121
	}
122
123
	/**
124
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
125
	 * the next time the user visits the site.
126
	 **/
127
	public function xmlrpc_user_disconnect( $user_id ) {
128
		$user_query = new WP_User_Query(
129
			array(
130
				'meta_key' => 'wpcom_user_id',
131
				'meta_value' => $user_id,
132
			)
133
		);
134
		$user = $user_query->get_results();
135
		$user = $user[0];
136
137
		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...
138
			$user = wp_set_current_user( $user->ID );
139
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
140
			self::delete_connection_for_user( $user->ID );
141
			return true;
142
		}
143
		return false;
144
	}
145
146
	/**
147
	 * Enqueues scripts and styles necessary for SSO login.
148
	 */
149
	public function login_enqueue_scripts() {
150
		global $action;
151
152
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
153
			return;
154
		}
155
156
		wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
157
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
158
	}
159
160
	/**
161
	 * Enqueue styles neceessary for Jetpack SSO on users' profiles
162
	 */
163
	public function admin_enqueue_scripts() {
164
		$screen = get_current_screen();
165
166
		if ( empty( $screen ) || ! in_array( $screen->base, array( 'edit-user', 'profile' ) ) ) {
167
			return;
168
		}
169
170
		wp_enqueue_style( 'jetpack-sso-profile', plugins_url( 'modules/sso/jetpack-sso-profile.css', JETPACK__PLUGIN_FILE ), array( 'genericons' ), JETPACK__VERSION );
171
	}
172
173
	/**
174
	 * Adds Jetpack SSO classes to login body
175
	 *
176
	 * @param  array $classes Array of classes to add to body tag
177
	 * @return array          Array of classes to add to body tag
178
	 */
179
	public function login_body_class( $classes ) {
180
		global $action;
181
182
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
183
			return $classes;
184
		}
185
186
		// If jetpack-sso-default-form, show the default login form.
187
		if ( isset( $_GET['jetpack-sso-default-form'] ) && 1 == $_GET['jetpack-sso-default-form'] ) {
188
			return $classes;
189
		}
190
191
		$classes[] = 'jetpack-sso-body';
192
		return $classes;
193
	}
194
195
	/**
196
	 * Adds settings fields to Settings > General > Single Sign On that allows users to
197
	 * turn off the login form on wp-login.php
198
	 *
199
	 * @since 2.7
200
	 **/
201
	public function register_settings() {
202
203
		add_settings_section(
204
			'jetpack_sso_settings',
205
			__( 'Single Sign On' , 'jetpack' ),
206
			'__return_false',
207
			'jetpack-sso'
208
		);
209
210
		/*
211
		 * Settings > General > Single Sign On
212
		 * Checkbox for Remove default login form
213
		 */
214
		 /* Hide in 2.9
215
		register_setting(
216
			'general',
217
			'jetpack_sso_remove_login_form',
218
			array( $this, 'validate_settings_remove_login_form_checkbox' )
219
		);
220
221
		add_settings_field(
222
			'jetpack_sso_remove_login_form',
223
			__( 'Remove default login form?' , 'jetpack' ),
224
			array( $this, 'render_remove_login_form_checkbox' ),
225
			'general',
226
			'jetpack_sso_settings'
227
		);
228
		*/
229
230
		/*
231
		 * Settings > General > Single Sign On
232
		 * Require two step authentication
233
		 */
234
		register_setting(
235
			'jetpack-sso',
236
			'jetpack_sso_require_two_step',
237
			array( $this, 'validate_jetpack_sso_require_two_step' )
238
		);
239
240
		add_settings_field(
241
			'jetpack_sso_require_two_step',
242
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
243
			array( $this, 'render_require_two_step' ),
244
			'jetpack-sso',
245
			'jetpack_sso_settings'
246
		);
247
248
		/*
249
		 * Settings > General > Single Sign On
250
		 */
251
		register_setting(
252
			'jetpack-sso',
253
			'jetpack_sso_match_by_email',
254
			array( $this, 'validate_jetpack_sso_match_by_email' )
255
		);
256
257
		add_settings_field(
258
			'jetpack_sso_match_by_email',
259
			'', // __( 'Match by Email' , 'jetpack' ),
260
			array( $this, 'render_match_by_email' ),
261
			'jetpack-sso',
262
			'jetpack_sso_settings'
263
		);
264
	}
265
266
	/**
267
	 * Builds the display for the checkbox allowing user to require two step
268
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
269
	 *
270
	 * @since 2.7
271
	 **/
272
	public function render_require_two_step() {
273
		/** This filter is documented in modules/sso.php */
274
		$require_two_step = ( 1 == apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) ) );
275
		$disabled = $require_two_step ? ' disabled="disabled"' : '';
276
		echo '<label>';
277
		echo '<input type="checkbox" name="jetpack_sso_require_two_step" ' . checked( $require_two_step, true, false ) . "$disabled>";
278
		esc_html_e( 'Require Two-Step Authentication' , 'jetpack' );
279
		echo '</label>';
280
	}
281
282
	/**
283
	 * Validate the require  two step checkbox in Settings > General
284
	 *
285
	 * @since 2.7
286
	 * @return boolean
287
	 **/
288
	public function validate_jetpack_sso_require_two_step( $input ) {
289
		return ( ! empty( $input ) ) ? 1 : 0;
290
	}
291
292
	/**
293
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
294
	 * Displays in Settings > General
295
	 *
296
	 * @since 2.9
297
	 **/
298
	public function render_match_by_email() {
299
		$match_by_email = 1 == $this->match_by_email();
300
		$disabled = $match_by_email ? ' disabled="disabled"' : '';
301
		echo '<label>';
302
		echo '<input type="checkbox" name="jetpack_sso_match_by_email"' . checked( $match_by_email, true, false ) . "$disabled>";
303
		esc_html_e( 'Match by Email', 'jetpack' );
304
		echo '</label>';
305
	}
306
307
	/**
308
	 * Validate the match by email check in Settings > General
309
	 *
310
	 * @since 2.9
311
	 * @return boolean
312
	 **/
313
	public function validate_jetpack_sso_match_by_email( $input ) {
314
		return ( ! empty( $input ) ) ? 1 : 0;
315
	}
316
317
	/**
318
	 * Builds the display for the checkbox allowing users to remove the default
319
	 * WordPress login form from wp-login.php. Displays in Settings > General
320
	 *
321
	 * @since 2.7
322
	 **/
323
	public function render_remove_login_form_checkbox() {
324
		if ( $this->is_user_connected( get_current_user_id() ) ) {
325
			echo '<a name="configure-sso"></a>';
326
			echo '<input type="checkbox" name="jetpack_sso_remove_login_form[remove_login_form]" ' . checked( 1 == get_option( 'jetpack_sso_remove_login_form' ), true, false ) . '>';
327
			echo '<p class="description">Removes default login form and disallows login via POST</p>';
328
		} else {
329
			echo 'Your account must be connected to WordPress.com before disabling the login form.';
330
			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...
331
		}
332
	}
333
334
	/**
335
	 * Validate settings input from Settings > General
336
	 *
337
	 * @since 2.7
338
	 * @return boolean
339
	 **/
340
	public function validate_settings_remove_login_form_checkbox( $input ) {
341
		return ( isset( $input['remove_login_form'] ) )? 1: 0;
342
	}
343
344
	/**
345
	 * Checks to determine if the user wants to login on wp-login
346
	 *
347
	 * This function mostly exists to cover the exceptions to login
348
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
349
	 * does not have to exist. By default WordPress assumes login if an action
350
	 * is not set, however this may not be true, as in the case of logout
351
	 * where $_GET[loggedout] is instead set
352
	 *
353
	 * @return boolean
354
	 **/
355
	private function wants_to_login() {
356
		$wants_to_login = false;
357
358
		// Cover default WordPress behavior
359
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
360
361
		// And now the exceptions
362
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
363
364
		if ( 'login' == $action ) {
365
			$wants_to_login = true;
366
		}
367
368
		return $wants_to_login;
369
	}
370
371
	private function bypass_login_forward_wpcom() {
372
		/**
373
		 * Redirect the site's log in form to WordPress.com's log in form.
374
		 *
375
		 * @module sso
376
		 *
377
		 * @since 3.1.0
378
		 *
379
		 * @param bool false Should the site's log in form be automatically forwarded to WordPress.com's log in form.
380
		 */
381
		return apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
382
	}
383
384
	function login_init() {
385
		global $action;
386
387
		/**
388
		 * If the user is attempting to logout AND the auto-forward to WordPress.com
389
		 * login is set then we need to ensure we do not auto-forward the user and get
390
		 * them stuck in an infinite logout loop.
391
		 */
392
		if ( isset( $_GET['loggedout'] ) && $this->bypass_login_forward_wpcom() ) {
393
			add_filter( 'jetpack_remove_login_form', '__return_true' );
394
		}
395
396
		/**
397
		 * Check to see if the site admin wants to automagically forward the user
398
		 * to the WordPress.com login page AND  that the request to wp-login.php
399
		 * is not something other than login (Like logout!)
400
		 */
401
		if (
402
			$this->wants_to_login()
403
			&& $this->bypass_login_forward_wpcom()
404
		) {
405
			add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
406
			$this->maybe_save_cookie_redirect();
407
			$reauth = ! empty( $_GET['reauth'] );
408
			wp_safe_redirect( $this->get_sso_url_or_die( $reauth ) );
409
			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...
410
		}
411
412
		if ( 'login' === $action ) {
413
			$this->display_sso_login_form();
414
		} elseif ( 'jetpack-sso' === $action ) {
415
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
416
				$this->handle_login();
417
				$this->display_sso_login_form();
418
			} else {
419
				if ( Jetpack::check_identity_crisis() ) {
420
					wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
421
				} else {
422
					$this->maybe_save_cookie_redirect();
423
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
424
					add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
425
					$reauth = ! empty( $_GET['reauth'] );
426
					wp_safe_redirect( $this->get_sso_url_or_die( $reauth ) );
427
					exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method login_init() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
428
				}
429
			}
430
		}
431
	}
432
433
	/**
434
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
435
	 * up the hooks required to display the SSO form.
436
	 */
437
	public function display_sso_login_form() {
438
		$sso_nonce = self::request_initial_nonce();
439
		if ( is_wp_error( $sso_nonce ) ) {
440
			return;
441
		}
442
443
		add_action( 'login_form',            array( $this, 'login_form' ) );
444
		add_filter( 'login_body_class',      array( $this, 'login_body_class' ) );
445
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
446
	}
447
448
	/**
449
	 * Conditionally save the redirect_to url as a cookie.
450
	 */
451
	public static function maybe_save_cookie_redirect() {
452
		if ( headers_sent() ) {
453
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
454
		}
455
456
		if ( ! empty( $_GET['redirect_to'] ) ) {
457
			// If we have something to redirect to
458
			$url = esc_url_raw( $_GET['redirect_to'] );
459
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
460
461
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
462
			// Otherwise, if it's already set, purge it.
463
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
464
		}
465
466
		if ( ! empty( $_GET['rememberme'] ) ) {
467
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
468
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
469
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
470
		}
471
	}
472
473
	/**
474
	 * Determine if the login form should be hidden or not
475
	 *
476
	 * Method is private only because it is only used in this class so far.
477
	 * Feel free to change it later
478
	 *
479
	 * @return bool
480
	 **/
481
	private function should_hide_login_form() {
482
		/**
483
		 * Remove the default log in form, only leave the WordPress.com log in button.
484
		 *
485
		 * @module sso
486
		 *
487
		 * @since 3.1.0
488
		 *
489
		 * @param bool get_option( 'jetpack_sso_remove_login_form', false ) Should the default log in form be removed. Default to false.
490
		 */
491
		return apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
492
	}
493
494
	/**
495
	 * Outputs the Jetpack SSO button and description as well as the toggle link
496
	 * for switching between Jetpack SSO and default login.
497
	 */
498
	function login_form() {
499
		$site_name = get_bloginfo( 'name' );
500
		if ( ! $site_name ) {
501
			$site_name = get_bloginfo( 'url' );
502
		}
503
504
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name' . COOKIEHASH ] )
505
			? $_COOKIE[ 'jetpack_sso_wpcom_name' . COOKIEHASH ]
506
			: false;
507
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar' . COOKIEHASH ] )
508
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar' . COOKIEHASH ]
509
			: false;
510
511
		?>
512
		<div id="jetpack-sso-wrap">
513
			<?php if ( $display_name && $gravatar ) : ?>
514
				<div id="jetpack-sso-wrap__user">
515
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
516
517
					<h2>
518
						<?php
519
							echo wp_kses(
520
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
521
								array( 'span' => true )
522
							);
523
						?>
524
					</h2>
525
				</div>
526
527
			<?php endif; ?>
528
529
530
			<div id="jetpack-sso-wrap__action">
531
				<?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...
532
533
				<?php if ( $display_name && $gravatar ) : ?>
534
					<a class="jetpack-sso-wrap__reauth" href="<?php echo $this->build_sso_button_url( array( 'reauth' => '1' ) ); ?>">
535
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
536
					</a>
537
				<?php else : ?>
538
					<p>
539
						<?php
540
							echo esc_html(
541
								sprintf(
542
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
543
									esc_html( $site_name )
544
								)
545
							);
546
						?>
547
					</p>
548
				<?php endif; ?>
549
			</div>
550
551
			<?php if ( ! $this->should_hide_login_form() ) : ?>
552
				<div class="jetpack-sso-or">
553
					<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
554
				</div>
555
556
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '1' ); ?>" class="jetpack-sso-toggle wpcom">
557
					<?php
558
						esc_html_e( 'Log in with username and password', 'jetpack' )
559
					?>
560
				</a>
561
562
				<a href="<?php echo add_query_arg( 'jetpack-sso-default-form', '0' ); ?>" class="jetpack-sso-toggle default">
563
					<?php
564
						esc_html_e( 'Log in with WordPress.com', 'jetpack' )
565
					?>
566
				</a>
567
			<?php endif; ?>
568
		</div>
569
		<?php
570
	}
571
572
	/**
573
	 * Clear the cookies that store the profile information for the last
574
	 * WPCOM user to connect.
575
	 */
576
	static function clear_wpcom_profile_cookies() {
577 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name' . COOKIEHASH ] ) ) {
578
			setcookie(
579
				'jetpack_sso_wpcom_name' . COOKIEHASH,
580
				' ',
581
				time() - YEAR_IN_SECONDS,
582
				COOKIEPATH,
583
				COOKIE_DOMAIN
584
			);
585
		}
586
587 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar' . COOKIEHASH ] ) ) {
588
			setcookie(
589
				'jetpack_sso_wpcom_gravatar' . COOKIEHASH,
590
				' ',
591
				time() - YEAR_IN_SECONDS,
592
				COOKIEPATH,
593
				COOKIE_DOMAIN
594
			);
595
		}
596
	}
597
598 View Code Duplication
	static function delete_connection_for_user( $user_id ) {
599
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
600
			return;
601
		}
602
		Jetpack::load_xml_rpc_client();
603
		$xml = new Jetpack_IXR_Client( array(
604
			'wpcom_user_id' => $user_id,
605
		) );
606
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
607
608
		if ( $xml->isError() ) {
609
			return false;
610
		}
611
612
		self::clear_wpcom_profile_cookies();
613
614
		return $xml->getResponse();
615
	}
616
617 View Code Duplication
	static function request_initial_nonce() {
618
		Jetpack::load_xml_rpc_client();
619
		$xml = new Jetpack_IXR_Client( array(
620
			'user_id' => get_current_user_id(),
621
		) );
622
		$xml->query( 'jetpack.sso.requestNonce' );
623
624
		if ( $xml->isError() ) {
625
			return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
626
		}
627
628
		return $xml->getResponse();
629
	}
630
631
	/**
632
	 * The function that actually handles the login!
633
	 */
634
	function handle_login() {
635
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
636
		$wpcom_user_id = (int) $_GET['user_id'];
637
		$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...
638
639
		Jetpack::load_xml_rpc_client();
640
		$xml = new Jetpack_IXR_Client( array(
641
			'user_id' => get_current_user_id(),
642
		) );
643
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
644
645
		if ( $xml->isError() ) {
646
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
647
		}
648
649
		$user_data = $xml->getResponse();
650
651
		if ( empty( $user_data ) ) {
652
			wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
653
		}
654
655
		$user_data = (object) $user_data;
656
		$user = null;
657
658
		/**
659
		 * Fires before Jetpack's SSO modifies the log in form.
660
		 *
661
		 * @module sso
662
		 *
663
		 * @since 2.6.0
664
		 *
665
		 * @param object $user_data User login information.
666
		 */
667
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
668
669
		/**
670
		 * Is it required to have 2-step authentication enabled on WordPress.com to use SSO?
671
		 *
672
		 * @module sso
673
		 *
674
		 * @since 2.8.0
675
		 *
676
		 * @param bool get_option( 'jetpack_sso_require_two_step' ) Does SSO require 2-step authentication?
677
		 */
678
		$require_two_step = apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) );
679
		if ( $require_two_step && 0 == (int) $user_data->two_step_enabled ) {
680
			$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...
681
			/** This filter is documented in core/src/wp-includes/pluggable.php */
682
			do_action( 'wp_login_failed', $user_data->login );
683
			add_action( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
684
			return;
685
		}
686
687
		if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
688
			list( $state, $nonce ) = explode( '|', $_GET['state'] );
689
690
			if ( wp_verify_nonce( $nonce, $state ) ) {
691
				if ( 'sso-link-user' == $state ) {
692
					$user = wp_get_current_user();
693
					update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
694
					add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
695
				}
696
			} else {
697
				wp_nonce_ays();
698
			}
699
		}
700
701
		if ( empty( $user ) ) {
702
			$user = $this->get_user_by_wpcom_id( $user_data->ID );
703
		}
704
705
		// If we don't have one by wpcom_user_id, try by the email?
706
		if ( empty( $user ) && self::match_by_email() ) {
707
			$user = get_user_by( 'email', $user_data->email );
708
			if ( $user ) {
709
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
710
			}
711
		}
712
713
		// If we've still got nothing, create the user.
714
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
715
			// If not matching by email we still need to verify the email does not exist
716
			// or this blows up
717
			/**
718
			 * If match_by_email is true, we know the email doesn't exist, as it would have
719
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
720
			 * user, then we know that email is unused, so it's safe to add.
721
			 */
722
			if ( self::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
723
				$username = $user_data->login;
724
725
				if ( username_exists( $username ) ) {
726
					$username = $user_data->login . '_' . $user_data->ID;
727
				}
728
729
				$tries = 0;
730
				while ( username_exists( $username ) ) {
731
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
732
					if ( $tries++ >= 5 ) {
733
						wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
734
					}
735
				}
736
737
				$password = wp_generate_password( 20 );
738
				$user_id  = wp_create_user( $username, $password, $user_data->email );
739
				$user     = get_userdata( $user_id );
740
741
				$user->display_name = $user_data->display_name;
742
				$user->first_name   = $user_data->first_name;
743
				$user->last_name    = $user_data->last_name;
744
				$user->url          = $user_data->url;
745
				$user->description  = $user_data->description;
746
				wp_update_user( $user );
747
748
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
749
			} else {
750
				$this->user_data = $user_data;
751
				// do_action( 'wp_login_failed', $user_data->login );
752
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
753
				return;
754
			}
755
		}
756
757
		/**
758
		 * Fires after we got login information from WordPress.com.
759
		 *
760
		 * @module sso
761
		 *
762
		 * @since 2.6.0
763
		 *
764
		 * @param array $user WordPress.com User information.
765
		 * @param object $user_data User Login information.
766
		 */
767
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
768
769
		if ( $user ) {
770
			// Cache the user's details, so we can present it back to them on their user screen
771
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
772
773
			// Cache user's display name and Gravatar so it can be displayed on the login screen
774
			setcookie(
775
				'jetpack_sso_wpcom_name' . COOKIEHASH,
776
				$user_data->display_name,
777
				time() + YEAR_IN_SECONDS,
778
				COOKIEPATH,
779
				COOKIE_DOMAIN
780
			);
781
782
			setcookie(
783
				'jetpack_sso_wpcom_gravatar' . COOKIEHASH,
784
				get_avatar_url(
785
					$user_data->email,
786
					array( 'size' => 72, 'default' => 'mystery' )
787
				),
788
				time() + YEAR_IN_SECONDS,
789
				COOKIEPATH,
790
				COOKIE_DOMAIN
791
			);
792
793
			$remember = false;
794 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
795
				$remember = true;
796
				// And then purge it
797
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
798
			}
799
			/**
800
			 * Filter the remember me value.
801
			 *
802
			 * @module sso
803
			 *
804
			 * @since 2.8.0
805
			 *
806
			 * @param bool $remember Is the remember me option checked?
807
			 */
808
			$remember = apply_filters( 'jetpack_remember_login', $remember );
809
			wp_set_auth_cookie( $user->ID, $remember );
810
811
			/** This filter is documented in core/src/wp-includes/user.php */
812
			do_action( 'wp_login', $user->user_login, $user );
813
814
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
815
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
816
817
			// If we have a saved redirect to request in a cookie
818 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
819
				// Set that as the requested redirect to
820
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
821
				// And then purge it
822
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
823
			}
824
825
			if ( ! Jetpack::is_user_connected( $user->ID ) ) {
826
				wp_safe_redirect(
827
					add_query_arg(
828
						array(
829
							'redirect_to'               => $redirect_to,
830
							'request_redirect_to'       => $_request_redirect_to,
831
							'jetpack-sso-auth-redirect' => '1',
832
						),
833
						admin_url()
834
					)
835
				);
836
				exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method handle_login() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
837
			}
838
839
			wp_safe_redirect(
840
				/** This filter is documented in core/src/wp-login.php */
841
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
842
			);
843
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method handle_login() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
844
		}
845
846
		$this->user_data = $user_data;
847
		/** This filter is documented in core/src/wp-includes/pluggable.php */
848
		do_action( 'wp_login_failed', $user_data->login );
849
		add_action( 'login_message', array( $this, 'cant_find_user' ) );
850
	}
851
852
	static function profile_page_url() {
853
		return admin_url( 'profile.php' );
854
	}
855
856
	static function match_by_email() {
857
		$match_by_email = ( 1 == get_option( 'jetpack_sso_match_by_email', true ) ) ? true: false;
858
		$match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : $match_by_email;
859
860
		/**
861
		 * Link the local account to an account on WordPress.com using the same email address.
862
		 *
863
		 * @module sso
864
		 *
865
		 * @since 2.6.0
866
		 *
867
		 * @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.
868
		 */
869
		return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
870
	}
871
872
	static function new_user_override() {
873
		$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
874
875
		/**
876
		 * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
877
		 *
878
		 * @module sso
879
		 *
880
		 * @since 2.6.0
881
		 *
882
		 * @param bool $new_user_override Allow users to register on your site with a WordPress.com account. Default to false.
883
		 */
884
		return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
885
	}
886
887
	function allowed_redirect_hosts( $hosts ) {
888
		if ( empty( $hosts ) ) {
889
			$hosts = array();
890
		}
891
		$hosts[] = 'wordpress.com';
892
		$hosts[] = 'jetpack.wordpress.com';
893
894
		return array_unique( $hosts );
895
	}
896
897
	/**
898
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
899
	 *
900
	 * @param  array   $args       An array of arguments to add to the SSO URL.
901
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
902
	 * @return string              Returns the HTML markup for the button.
903
	 */
904
	function build_sso_button( $args = array(), $is_primary = false ) {
905
		$url = $this->build_sso_button_url( $args );
906
		$classes = $is_primary
907
			? 'jetpack-sso button button-primary'
908
			: 'jetpack-sso button';
909
910
		return sprintf(
911
			'<a href="%1$s" class="%2$s">%3$s</a>',
912
			esc_url( $url ),
913
			$classes,
914
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
915
		);
916
	}
917
918
	/**
919
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
920
	 *
921
	 * @param  array  $args An array of arguments to add to the SSO URL.
922
	 * @return string       The URL used for SSO.
923
	 */
924
	function build_sso_button_url( $args = array() ) {
925
		$defaults = array(
926
			'action'  => 'jetpack-sso',
927
		);
928
929
		$args = wp_parse_args( $args, $defaults );
930
931
		if ( ! empty( $_GET['redirect_to'] ) ) {
932
			$args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
933
		}
934
935
		return add_query_arg( $args, wp_login_url() );
936
	}
937
938
	/**
939
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
940
	 *
941
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
942
	 * @param  array    $args    Optional query parameters.
943
	 * @return string            The WordPress.com SSO URL.
944
	 */
945
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
946
		if ( empty( $reauth ) ) {
947
			$sso_redirect = $this->build_sso_url( $args );
948
		} else {
949
			self::clear_wpcom_profile_cookies();
950
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
951
		}
952
953
		// If there was an error retrieving the SSO URL, then error.
954
		if ( is_wp_error( $sso_redirect ) ) {
955
			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...
956
		}
957
958
		return $sso_redirect;
959
	}
960
961
	/**
962
	 * Build WordPress.com SSO URL with appropriate query parameters.
963
	 *
964
	 * @param  array  $args Optional query parameters.
965
	 * @return string       WordPress.com SSO URL
966
	 */
967
	function build_sso_url( $args = array() ) {
968
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
969
		$defaults = array(
970
			'action'    => 'jetpack-sso',
971
			'site_id'   => Jetpack_Options::get_option( 'id' ),
972
			'sso_nonce' => $sso_nonce,
973
		);
974
975
		if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
976
			$defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
977
		}
978
979
		$args = wp_parse_args( $args, $defaults );
980
981
		if ( is_wp_error( $args['sso_nonce'] ) ) {
982
			return $args['sso_nonce'];
983
		}
984
985
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
986
	}
987
988
	/**
989
	 * Build WordPress.com SSO URL with appropriate query parameters,
990
	 * including the parameters necessary to force the user to reauthenticate
991
	 * on WordPress.com.
992
	 *
993
	 * @param  array  $args Optional query parameters.
994
	 * @return string       WordPress.com SSO URL
995
	 */
996
	function build_reauth_and_sso_url( $args = array() ) {
997
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
998
999
		if ( is_wp_error( $redirect ) ) {
0 ignored issues
show
Bug introduced by
The variable $redirect seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1000
			return $redirect;
0 ignored issues
show
Bug introduced by
The variable $redirect seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1001
		}
1002
1003
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
1004
		$defaults = array(
1005
			'action'      => 'jetpack-sso',
1006
			'site_id'     => Jetpack_Options::get_option( 'id' ),
1007
			'sso_nonce'   => $sso_nonce,
1008
			'reauth'      => '1',
1009
			'redirect_to' => urlencode( $redirect ),
1010
		);
1011
1012
		$args = wp_parse_args( $args, $defaults );
1013
1014
		if ( is_wp_error( $args['sso_nonce'] ) ) {
1015
			return $args['sso_nonce'];
1016
		}
1017
1018
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
1019
	}
1020
1021
	/**
1022
	 * Determines local user associated with a given WordPress.com user ID.
1023
	 *
1024
	 * @since 2.6.0
1025
	 *
1026
	 * @param int $wpcom_user_id User ID from WordPress.com
1027
	 * @return object Local user object if found, null if not.
1028
	 */
1029
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
1030
		$user_query = new WP_User_Query( array(
1031
			'meta_key'   => 'wpcom_user_id',
1032
			'meta_value' => intval( $wpcom_user_id ),
1033
			'number'     => 1,
1034
		) );
1035
1036
		$users = $user_query->get_results();
1037
		return $users ? array_shift( $users ) : null;
1038
	}
1039
1040
	/**
1041
	 * Error message displayed on the login form when two step is required and
1042
	 * the user's account on WordPress.com does not have two step enabled.
1043
	 *
1044
	 * @since 2.7
1045
	 * @param string $message
1046
	 * @return string
1047
	 **/
1048
	public function error_msg_enable_two_step( $message ) {
1049
		$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' );
1050
1051
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1052
1053
		return $message;
1054
	}
1055
1056
	/**
1057
	 * Error message displayed when the user tries to SSO, but match by email
1058
	 * is off and they already have an account with their email address on
1059
	 * this site.
1060
	 *
1061
	 * @param string $message
1062
	 * @return string
1063
	 */
1064
	public function error_msg_email_already_exists( $message ) {
1065
		$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' );
1066
1067
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1068
1069
		return $message;
1070
	}
1071
1072
	/**
1073
	 * Message displayed when the site admin has disabled the default WordPress
1074
	 * login form in Settings > General > Single Sign On
1075
	 *
1076
	 * @since 2.7
1077
	 * @param string $message
1078
	 * @return string
1079
	 **/
1080
	public function msg_login_by_jetpack( $message ) {
1081
1082
		$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' );
1083
1084
		/**
1085
		 * Filter the message displayed when the default WordPress login form is disabled.
1086
		 *
1087
		 * @module sso
1088
		 *
1089
		 * @since 2.8.0
1090
		 *
1091
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
1092
		 */
1093
		$msg = apply_filters( 'jetpack_sso_disclaimer_message', $msg );
1094
1095
		$message .= sprintf( '<p class="message">%s</p>', $msg );
1096
		return $message;
1097
	}
1098
1099
	/**
1100
	 * Error message displayed on the login form when the user attempts
1101
	 * to post to the login form and it is disabled.
1102
	 *
1103
	 * @since 2.8
1104
	 * @param string $message
1105
	 * @param string
1106
	 **/
1107
	public function error_msg_login_method_not_allowed( $message ) {
1108
		$err = __( 'Login method not allowed' , 'jetpack' );
1109
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1110
1111
		return $message;
1112
	}
1113
	function cant_find_user( $message ) {
1114
		if ( self::match_by_email() ) {
1115
			$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' );
1116
		} else {
1117
			$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' );
1118
		}
1119
		$err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
1120
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
1121
		return $message;
1122
	}
1123
1124
	/**
1125
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
1126
	 * WordPress.com authorization flow.
1127
	 *
1128
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1129
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1130
	 */
1131
	function maybe_authorize_user_after_sso() {
1132
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1133
			return;
1134
		}
1135
1136
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? $_GET['redirect_to'] : admin_url();
1137
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? $_GET['request_redirect_to'] : $redirect_to;
1138
1139
		/** This filter is documented in core/src/wp-login.php */
1140
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
1141
1142
		/**
1143
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1144
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1145
		 */
1146
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1147
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1148
1149
		/**
1150
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1151
		 */
1152
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1153
1154
		add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
1155
		wp_safe_redirect( $connect_url );
1156
		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...
1157
	}
1158
1159
	/**
1160
	 * Deal with user connections...
1161
	 */
1162
	function admin_init() {
1163
		add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
1164
		add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
1165
1166
		if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
1167
			$user = wp_get_current_user();
1168
			// Remove the connection on the wpcom end.
1169
			self::delete_connection_for_user( $user->ID );
1170
			// Clear it locally.
1171
			delete_user_meta( $user->ID, 'wpcom_user_id' );
1172
			delete_user_meta( $user->ID, 'wpcom_user_data' );
1173
			// Forward back to the profile page.
1174
			wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
1175
			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...
1176
		}
1177
	}
1178
1179
	/**
1180
	 * Determines if a local user is connected to WordPress.com
1181
	 *
1182
	 * @since 2.8
1183
	 * @param integer $user_id - Local user id
1184
	 * @return boolean
1185
	 **/
1186
	public function is_user_connected( $user_id ) {
1187
		return $this->get_user_data( $user_id );
1188
	}
1189
1190
	/**
1191
	 * Retrieves a user's WordPress.com data
1192
	 *
1193
	 * @since 2.8
1194
	 * @param integer $user_id - Local user id
1195
	 * @return mixed null or stdClass
1196
	 **/
1197
	public function get_user_data( $user_id ) {
1198
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1199
	}
1200
1201
	function edit_profile_fields( $user ) {
1202
		?>
1203
1204
		<h3 id="single-sign-on"><?php _e( 'Single Sign On', 'jetpack' ); ?></h3>
1205
		<p><?php _e( 'Connecting with Single Sign On enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
1206
1207
		<?php if ( $this->is_user_connected( $user->ID ) ) : /* If the user is currently connected... */ ?>
1208
			<?php $user_data = $this->get_user_data( $user->ID ); ?>
1209
			<table class="form-table jetpack-sso-form-table">
1210
				<tbody>
1211
					<tr>
1212
						<td>
1213
							<div class="profile-card">
1214
								<?php echo get_avatar( $user_data->email ); ?>
1215
								<p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
1216
								<p><?php echo esc_html( $user_data->login ); ?></p>
1217
								<span class="two_step">
1218
									<?php
1219
										if ( $user_data->two_step_enabled ) {
1220
											?> <p class="enabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Enabled', 'jetpack' ); ?></a></p> <?php
1221
										} else {
1222
											?> <p class="disabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Disabled', 'jetpack' ); ?></a></p> <?php
1223
										}
1224
									?>
1225
								</span>
1226
1227
							</div>
1228
							<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>
1229
						</td>
1230
					</tr>
1231
				</tbody>
1232
			</table>
1233
		<?php elseif ( get_current_user_id() == $user->ID && Jetpack::is_user_connected( $user->ID ) ) : ?>
1234
1235
			<?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...
1236
1237
		<?php else : ?>
1238
1239
			<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>
1240
			<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>
1241
1242
		<?php endif;
1243
	}
1244
}
1245
1246
Jetpack_SSO::get_instance();
1247