Completed
Push — update/jetpack-sso-mercury ( 5416cc...32c031 )
by
unknown
13:14 queued 03:11
created

Jetpack_SSO::login_form()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
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, 'admin_init' ) );
25
		add_action( 'admin_init',  array( $this, 'register_settings' ) );
26
		add_action( 'login_init',  array( $this, 'login_init' ) );
27
		add_action( 'delete_user', array( $this, 'delete_connection_for_user' ) );
28
		add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
29
		add_action( 'init', array( $this, 'maybe_logout_user' ), 5 );
30
		add_action( 'jetpack_modules_loaded', array( $this, 'module_configure_button' ) );
31
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
32
33
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
34
		add_action( 'login_form_jetpack-sso', '__return_true' );
35
36
		if (
37
			$this->should_hide_login_form() &&
38
			/**
39
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
40
			 *
41
			 * @module sso
42
			 *
43
			 * @since 2.8.0
44
			 *
45
			 * @param bool true Should the disclaimer be displayed. Default to true.
46
			 */
47
			apply_filters( 'jetpack_sso_display_disclaimer', true )
48
		) {
49
			add_action( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
50
		}
51
	}
52
53
	/**
54
	 * Returns the single instance of the Jetpack_SSO object
55
	 *
56
	 * @since 2.8
57
	 * @return Jetpack_SSO
58
	 **/
59
	public static function get_instance() {
60
		if ( ! is_null( self::$instance ) ) {
61
			return self::$instance;
62
		}
63
64
		return self::$instance = new Jetpack_SSO;
65
	}
66
67
	/**
68
	 * Add configure button and functionality to the module card on the Jetpack screen
69
	 **/
70
	public static function module_configure_button() {
71
		Jetpack::enable_module_configurable( __FILE__ );
72
		Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
73
		Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
74
		Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
75
	}
76
77
	public static function module_configuration_load() {
78
		// wp_safe_redirect( admin_url( 'options-general.php#configure-sso' ) );
79
		// exit;
80
	}
81
82
	public static function module_configuration_head() {}
83
84
	public static function module_configuration_screen() {
85
		?>
86
		<form method="post" action="options.php">
87
			<?php settings_fields( 'jetpack-sso' ); ?>
88
			<?php do_settings_sections( 'jetpack-sso' ); ?>
89
			<?php submit_button(); ?>
90
		</form>
91
		<?php
92
	}
93
94
	/**
95
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
96
	 * to logout and reauthenticate with the site.
97
	 **/
98
	public function maybe_logout_user() {
99
		global $current_user;
100
101
		if ( 1 == $current_user->jetpack_force_logout ) {
102
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
103
			self::delete_connection_for_user( $current_user->ID );
104
			wp_logout();
105
			wp_safe_redirect( wp_login_url() );
106
		}
107
	}
108
109
110
	/**
111
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
112
	 *
113
	 * @param array $methods
114
	 * @return array
115
	 **/
116
	public function xmlrpc_methods( $methods ) {
117
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
118
		return $methods;
119
	}
120
121
	/**
122
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
123
	 * the next time the user visits the site.
124
	 **/
125
	public function xmlrpc_user_disconnect( $user_id ) {
126
		$user_query = new WP_User_Query(
127
			array(
128
				'meta_key' => 'wpcom_user_id',
129
				'meta_value' => $user_id,
130
			)
131
		);
132
		$user = $user_query->get_results();
133
		$user = $user[0];
134
135
		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...
136
			$user = wp_set_current_user( $user->ID );
137
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
138
			self::delete_connection_for_user( $user->ID );
139
			return true;
140
		}
141
		return false;
142
	}
143
144
	/**
145
	 * Enqueues scripts and styles necessary for SSO login.
146
	 */
147
	public function login_enqueue_scripts() {
148
		global $action;
149
150
		if ( ! in_array( $action, array( 'jetpack-sso', 'login' ) ) ) {
151
			return;
152
		}
153
154
		wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
155
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
156
	}
157
158
	/**
159
	 * Adds settings fields to Settings > General > Single Sign On that allows users to
160
	 * turn off the login form on wp-login.php
161
	 *
162
	 * @since 2.7
163
	 **/
164
	public function register_settings() {
165
166
		add_settings_section(
167
			'jetpack_sso_settings',
168
			__( 'Single Sign On' , 'jetpack' ),
169
			'__return_false',
170
			'jetpack-sso'
171
		);
172
173
		/*
174
		 * Settings > General > Single Sign On
175
		 * Checkbox for Remove default login form
176
		 */
177
		 /* Hide in 2.9
178
		register_setting(
179
			'general',
180
			'jetpack_sso_remove_login_form',
181
			array( $this, 'validate_settings_remove_login_form_checkbox' )
182
		);
183
184
		add_settings_field(
185
			'jetpack_sso_remove_login_form',
186
			__( 'Remove default login form?' , 'jetpack' ),
187
			array( $this, 'render_remove_login_form_checkbox' ),
188
			'general',
189
			'jetpack_sso_settings'
190
		);
191
		*/
192
193
		/*
194
		 * Settings > General > Single Sign On
195
		 * Require two step authentication
196
		 */
197
		register_setting(
198
			'jetpack-sso',
199
			'jetpack_sso_require_two_step',
200
			array( $this, 'validate_jetpack_sso_require_two_step' )
201
		);
202
203
		add_settings_field(
204
			'jetpack_sso_require_two_step',
205
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
206
			array( $this, 'render_require_two_step' ),
207
			'jetpack-sso',
208
			'jetpack_sso_settings'
209
		);
210
211
		/*
212
		 * Settings > General > Single Sign On
213
		 */
214
		register_setting(
215
			'jetpack-sso',
216
			'jetpack_sso_match_by_email',
217
			array( $this, 'validate_jetpack_sso_match_by_email' )
218
		);
219
220
		add_settings_field(
221
			'jetpack_sso_match_by_email',
222
			'', // __( 'Match by Email' , 'jetpack' ),
223
			array( $this, 'render_match_by_email' ),
224
			'jetpack-sso',
225
			'jetpack_sso_settings'
226
		);
227
	}
228
229
	/**
230
	 * Builds the display for the checkbox allowing user to require two step
231
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
232
	 *
233
	 * @since 2.7
234
	 **/
235
	public function render_require_two_step() {
236
		/** This filter is documented in modules/sso.php */
237
		$require_two_step = ( 1 == apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) ) );
238
		$disabled = $require_two_step ? ' disabled="disabled"' : '';
239
		echo '<label>';
240
		echo '<input type="checkbox" name="jetpack_sso_require_two_step" ' . checked( $require_two_step, true, false ) . "$disabled>";
241
		esc_html_e( 'Require Two-Step Authentication' , 'jetpack' );
242
		echo '</label>';
243
	}
244
245
	/**
246
	 * Validate the require  two step checkbox in Settings > General
247
	 *
248
	 * @since 2.7
249
	 * @return boolean
250
	 **/
251
	public function validate_jetpack_sso_require_two_step( $input ) {
252
		return ( ! empty( $input ) ) ? 1 : 0;
253
	}
254
255
	/**
256
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
257
	 * Displays in Settings > General
258
	 *
259
	 * @since 2.9
260
	 **/
261
	public function render_match_by_email() {
262
		$match_by_email = 1 == $this->match_by_email();
263
		$disabled = $match_by_email ? ' disabled="disabled"' : '';
264
		echo '<label>';
265
		echo '<input type="checkbox" name="jetpack_sso_match_by_email"' . checked( $match_by_email, true, false ) . "$disabled>";
266
		esc_html_e( 'Match by Email', 'jetpack' );
267
		echo '</label>';
268
	}
269
270
	/**
271
	 * Validate the match by email check in Settings > General
272
	 *
273
	 * @since 2.9
274
	 * @return boolean
275
	 **/
276
	public function validate_jetpack_sso_match_by_email( $input ) {
277
		return ( ! empty( $input ) ) ? 1 : 0;
278
	}
279
280
	/**
281
	 * Builds the display for the checkbox allowing users to remove the default
282
	 * WordPress login form from wp-login.php. Displays in Settings > General
283
	 *
284
	 * @since 2.7
285
	 **/
286
	public function render_remove_login_form_checkbox() {
287
		if ( $this->is_user_connected( get_current_user_id() ) ) {
288
			echo '<a name="configure-sso"></a>';
289
			echo '<input type="checkbox" name="jetpack_sso_remove_login_form[remove_login_form]" ' . checked( 1 == get_option( 'jetpack_sso_remove_login_form' ), true, false ) . '>';
290
			echo '<p class="description">Removes default login form and disallows login via POST</p>';
291
		} else {
292
			echo 'Your account must be connected to WordPress.com before disabling the login form.';
293
			echo '<br/>' . $this->button();
294
		}
295
	}
296
297
	/**
298
	 * Validate settings input from Settings > General
299
	 *
300
	 * @since 2.7
301
	 * @return boolean
302
	 **/
303
	public function validate_settings_remove_login_form_checkbox( $input ) {
304
		return ( isset( $input['remove_login_form'] ) )? 1: 0;
305
	}
306
307
	/**
308
	 * Removes 'Lost your password?' text from the login form if user
309
	 * does not want to show the login form
310
	 *
311
	 * @since 2.7
312
	 * @return string
313
	 **/
314
	public function remove_lost_password_text( $text ) {
315
		if ( 'Lost your password?' == $text ) {
316
			$text = '';
317
		}
318
		return $text;
319
	}
320
321
	/**
322
	 * Checks to determine if the user wants to login on wp-login
323
	 *
324
	 * This function mostly exists to cover the exceptions to login
325
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
326
	 * does not have to exist. By default WordPress assumes login if an action
327
	 * is not set, however this may not be true, as in the case of logout
328
	 * where $_GET[loggedout] is instead set
329
	 *
330
	 * @return boolean
331
	 **/
332
	private function wants_to_login() {
333
		$wants_to_login = false;
334
335
		// Cover default WordPress behavior
336
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
337
338
		// And now the exceptions
339
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
340
341
		if ( 'login' == $action ) {
342
			$wants_to_login = true;
343
		}
344
345
		return $wants_to_login;
346
	}
347
348
	private function bypass_login_forward_wpcom() {
349
		/**
350
		 * Redirect the site's log in form to WordPress.com's log in form.
351
		 *
352
		 * @module sso
353
		 *
354
		 * @since 3.1.0
355
		 *
356
		 * @param bool false Should the site's log in form be automatically forwarded to WordPress.com's log in form.
357
		 */
358
		return apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
359
	}
360
361
	function login_init() {
362
		global $action;
363
364
		/**
365
		 * If the user is attempting to logout AND the auto-forward to WordPress.com
366
		 * login is set then we need to ensure we do not auto-forward the user and get
367
		 * them stuck in an infinite logout loop.
368
		 */
369
		if ( isset( $_GET['loggedout'] ) && $this->bypass_login_forward_wpcom() ) {
370
			add_filter( 'jetpack_remove_login_form', '__return_true' );
371
			add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
372
		}
373
374
		/**
375
		 * Check to see if the site admin wants to automagically forward the user
376
		 * to the WordPress.com login page AND  that the request to wp-login.php
377
		 * is not something other than login (Like logout!)
378
		 */
379
		if (
380
			$this->wants_to_login()
381
			&& $this->bypass_login_forward_wpcom()
382
		) {
383
			add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
384
			$this->maybe_save_cookie_redirect();
385
			wp_safe_redirect( $this->build_sso_url() );
386
		}
387
388
		if ( 'login' === $action ) {
389
			add_action( 'login_footer', array( $this, 'login_form' ) );
390
391
			/*
392
			if ( get_option( 'jetpack_sso_remove_login_form' ) ) {
393
				// Check to see if the user is attempting to login via the default login form.
394
				// If so we need to deny it and forward elsewhere.
395
				if( isset( $_REQUEST['wp-submit'] ) && 'Log In' == $_REQUEST['wp-submit'] ) {
396
					wp_die( 'Login not permitted by this method. ');
397
				}
398
				add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
399
			}
400
			*/
401
		} elseif ( 'jetpack-sso' === $action ) {
402
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
403
				$this->handle_login();
404
				add_action( 'login_footer', array( $this, 'login_form' ) );
405
			} else {
406
				if ( Jetpack::check_identity_crisis() ) {
407
					wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
408
				} else {
409
					$this->maybe_save_cookie_redirect();
410
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
411
					add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
412
					wp_safe_redirect( $this->build_sso_url() );
413
				}
414
			}
415
		}
416
	}
417
418
	/**
419
	 * Conditionally save the redirect_to url as a cookie.
420
	 */
421
	public static function maybe_save_cookie_redirect() {
422
		if ( headers_sent() ) {
423
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
424
		}
425
426
		if ( ! empty( $_GET['redirect_to'] ) ) {
427
			// If we have something to redirect to
428
			$url = esc_url_raw( $_GET['redirect_to'] );
429
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
430
431
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
432
			// Otherwise, if it's already set, purge it.
433
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
434
		}
435
436
		if ( ! empty( $_GET['rememberme'] ) ) {
437
			setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
438
		} elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
439
			setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
440
		}
441
	}
442
443
	/**
444
	 * Determine if the login form should be hidden or not
445
	 *
446
	 * Method is private only because it is only used in this class so far.
447
	 * Feel free to change it later
448
	 *
449
	 * @return bool
450
	 **/
451
	private function should_hide_login_form() {
452
		/**
453
		 * Remove the default log in form, only leave the WordPress.com log in button.
454
		 *
455
		 * @module sso
456
		 *
457
		 * @since 3.1.0
458
		 *
459
		 * @param bool get_option( 'jetpack_sso_remove_login_form', false ) Should the default log in form be removed. Default to false.
460
		 */
461
		return apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
462
	}
463
464
	function login_form() {
465
		$classes = '';
466
467
		if ( $this->should_hide_login_form() ) {
468
			$classes .= ' forced-sso';
469
		}
470
		echo '<div class="jetpack-sso-wrap' . $classes . '">' . $this->button() . '</div>';
471
	}
472
473 View Code Duplication
	static function delete_connection_for_user( $user_id ) {
474
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
475
			return;
476
		}
477
		Jetpack::load_xml_rpc_client();
478
		$xml = new Jetpack_IXR_Client( array(
479
			'user_id' => $user_id,
480
		) );
481
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
482
483
		if ( $xml->isError() ) {
484
			return false;
485
		}
486
487
		return $xml->getResponse();
488
	}
489
490 View Code Duplication
	static function request_initial_nonce() {
491
		Jetpack::load_xml_rpc_client();
492
		$xml = new Jetpack_IXR_Client( array(
493
			'user_id' => get_current_user_id(),
494
		) );
495
		$xml->query( 'jetpack.sso.requestNonce' );
496
497
		if ( $xml->isError() ) {
498
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
499
		}
500
501
		return $xml->getResponse();
502
	}
503
504
	/**
505
	 * The function that actually handles the login!
506
	 */
507
	function handle_login() {
508
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
509
		$wpcom_user_id = (int) $_GET['user_id'];
510
		$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...
511
512
		Jetpack::load_xml_rpc_client();
513
		$xml = new Jetpack_IXR_Client( array(
514
			'user_id' => get_current_user_id(),
515
		) );
516
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
517
518
		if ( $xml->isError() ) {
519
			wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
520
		}
521
522
		$user_data = $xml->getResponse();
523
524
		if ( empty( $user_data ) ) {
525
			wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
526
		}
527
528
		$user_data = (object) $user_data;
529
		$user = null;
530
531
		/**
532
		 * Fires before Jetpack's SSO modifies the log in form.
533
		 *
534
		 * @module sso
535
		 *
536
		 * @since 2.6.0
537
		 *
538
		 * @param object $user_data User login information.
539
		 */
540
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
541
542
		/**
543
		 * Is it required to have 2-step authentication enabled on WordPress.com to use SSO?
544
		 *
545
		 * @module sso
546
		 *
547
		 * @since 2.8.0
548
		 *
549
		 * @param bool get_option( 'jetpack_sso_require_two_step' ) Does SSO require 2-step authentication?
550
		 */
551
		$require_two_step = apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) );
552
		if ( $require_two_step && 0 == (int) $user_data->two_step_enabled ) {
553
			$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...
554
			/** This filter is documented in core/src/wp-includes/pluggable.php */
555
			do_action( 'wp_login_failed', $user_data->login );
556
			add_action( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
557
			return;
558
		}
559
560
		if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
561
			list( $state, $nonce ) = explode( '|', $_GET['state'] );
562
563
			if ( wp_verify_nonce( $nonce, $state ) ) {
564
				if ( 'sso-link-user' == $state ) {
565
					$user = wp_get_current_user();
566
					update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
567
					add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
568
				}
569
			} else {
570
				wp_nonce_ays();
571
			}
572
		}
573
574
		if ( empty( $user ) ) {
575
			$user = $this->get_user_by_wpcom_id( $user_data->ID );
576
		}
577
578
		// If we don't have one by wpcom_user_id, try by the email?
579
		if ( empty( $user ) && self::match_by_email() ) {
580
			$user = get_user_by( 'email', $user_data->email );
581
			if ( $user ) {
582
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
583
			}
584
		}
585
586
		// If we've still got nothing, create the user.
587
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
588
			// If not matching by email we still need to verify the email does not exist
589
			// or this blows up
590
			/**
591
			 * If match_by_email is true, we know the email doesn't exist, as it would have
592
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
593
			 * user, then we know that email is unused, so it's safe to add.
594
			 */
595
			if ( self::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
596
				$username = $user_data->login;
597
598
				if ( username_exists( $username ) ) {
599
					$username = $user_data->login . '_' . $user_data->ID;
600
				}
601
602
				$tries = 0;
603
				while ( username_exists( $username ) ) {
604
					$username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
605
					if ( $tries++ >= 5 ) {
606
						wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
607
					}
608
				}
609
610
				$password = wp_generate_password( 20 );
611
				$user_id  = wp_create_user( $username, $password, $user_data->email );
612
				$user     = get_userdata( $user_id );
613
614
				$user->display_name = $user_data->display_name;
615
				$user->first_name   = $user_data->first_name;
616
				$user->last_name    = $user_data->last_name;
617
				$user->url          = $user_data->url;
618
				$user->description  = $user_data->description;
619
				wp_update_user( $user );
620
621
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
622
			} else {
623
				$this->user_data = $user_data;
624
				// do_action( 'wp_login_failed', $user_data->login );
625
				add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
626
				return;
627
			}
628
		}
629
630
		/**
631
		 * Fires after we got login information from WordPress.com.
632
		 *
633
		 * @module sso
634
		 *
635
		 * @since 2.6.0
636
		 *
637
		 * @param array $user WordPress.com User information.
638
		 * @param object $user_data User Login information.
639
		 */
640
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
641
642
		if ( $user ) {
643
			// Cache the user's details, so we can present it back to them on their user screen.
644
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
645
646
			$remember = false;
647 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
648
				$remember = true;
649
				// And then purge it
650
				setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
651
			}
652
			/**
653
			 * Filter the remember me value.
654
			 *
655
			 * @module sso
656
			 *
657
			 * @since 2.8.0
658
			 *
659
			 * @param bool $remember Is the remember me option checked?
660
			 */
661
			$remember = apply_filters( 'jetpack_remember_login', $remember );
662
			wp_set_auth_cookie( $user->ID, $remember );
663
664
			/** This filter is documented in core/src/wp-includes/user.php */
665
			do_action( 'wp_login', $user->user_login, $user );
666
667
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
668
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
669
670
			// If we have a saved redirect to request in a cookie
671 View Code Duplication
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
672
				// Set that as the requested redirect to
673
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
674
				// And then purge it
675
				setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
676
			}
677
678
			wp_safe_redirect(
679
				/** This filter is documented in core/src/wp-login.php */
680
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
681
			);
682
			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...
683
		}
684
685
		$this->user_data = $user_data;
686
		/** This filter is documented in core/src/wp-includes/pluggable.php */
687
		do_action( 'wp_login_failed', $user_data->login );
688
		add_action( 'login_message', array( $this, 'cant_find_user' ) );
689
	}
690
691
	static function profile_page_url() {
692
		return admin_url( 'profile.php' );
693
	}
694
695
	static function match_by_email() {
696
		$match_by_email = ( 1 == get_option( 'jetpack_sso_match_by_email', true ) ) ? true: false;
697
		$match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : $match_by_email;
698
699
		/**
700
		 * Link the local account to an account on WordPress.com using the same email address.
701
		 *
702
		 * @module sso
703
		 *
704
		 * @since 2.6.0
705
		 *
706
		 * @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.
707
		 */
708
		return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
709
	}
710
711
	static function new_user_override() {
712
		$new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
713
714
		/**
715
		 * Allow users to register on your site with a WordPress.com account, even though you disallow normal registrations.
716
		 *
717
		 * @module sso
718
		 *
719
		 * @since 2.6.0
720
		 *
721
		 * @param bool $new_user_override Allow users to register on your site with a WordPress.com account. Default to false.
722
		 */
723
		return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
724
	}
725
726
	function allowed_redirect_hosts( $hosts ) {
727
		if ( empty( $hosts ) ) {
728
			$hosts = array();
729
		}
730
		$hosts[] = 'wordpress.com';
731
732
		return array_unique( $hosts );
733
	}
734
735
	function button( $args = array() ) {
736
		$defaults = array(
737
			'action'  => 'jetpack-sso',
738
		);
739
740
		$args = wp_parse_args( $args, $defaults );
741
742
		if ( ! empty( $_GET['redirect_to'] ) ) {
743
			$args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
744
		}
745
746
		$url  = add_query_arg( $args, wp_login_url() );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 2 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
747
748
		$button = sprintf( '<a href="%1$s" class="jetpack-sso button">%2$s</a>', esc_url( $url ), esc_html__( 'Log in with WordPress.com', 'jetpack' ) );
749
		return $button;
750
	}
751
752
	function build_sso_url( $args = array() ) {
753
		$defaults = array(
754
			'action'    => 'jetpack-sso',
755
			'site_id'   => Jetpack_Options::get_option( 'id' ),
756
			'sso_nonce' => self::request_initial_nonce(),
757
		);
758
759
		if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
760
			$defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
761
		}
762
763
		$args = wp_parse_args( $args, $defaults );
764
		$url  = add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
765
766
		return $url;
767
	}
768
769
	/**
770
	 * Determines local user associated with a given WordPress.com user ID.
771
	 *
772
	 * @since 2.6.0
773
	 *
774
	 * @param int $wpcom_user_id User ID from WordPress.com
775
	 * @return object Local user object if found, null if not.
776
	 */
777
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
778
		$user_query = new WP_User_Query( array(
779
			'meta_key'   => 'wpcom_user_id',
780
			'meta_value' => intval( $wpcom_user_id ),
781
			'number'     => 1,
782
		) );
783
784
		$users = $user_query->get_results();
785
		return $users ? array_shift( $users ) : null;
786
	}
787
788
	/**
789
	 * Error message displayed on the login form when two step is required and
790
	 * the user's account on WordPress.com does not have two step enabled.
791
	 *
792
	 * @since 2.7
793
	 * @param string $message
794
	 * @return string
795
	 **/
796
	public function error_msg_enable_two_step( $message ) {
797
		$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' );
798
799
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
800
801
		return $message;
802
	}
803
804
	/**
805
	 * Error message displayed when the user tries to SSO, but match by email
806
	 * is off and they already have an account with their email address on
807
	 * this site.
808
	 *
809
	 * @param string $message
810
	 * @return string
811
	 */
812
	public function error_msg_email_already_exists( $message ) {
813
		$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' );
814
815
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
816
817
		return $message;
818
	}
819
820
	/**
821
	 * Message displayed when the site admin has disabled the default WordPress
822
	 * login form in Settings > General > Single Sign On
823
	 *
824
	 * @since 2.7
825
	 * @param string $message
826
	 * @return string
827
	 **/
828
	public function msg_login_by_jetpack( $message ) {
829
830
		$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' );
831
832
		/**
833
		 * Filter the message displayed when the default WordPress login form is disabled.
834
		 *
835
		 * @module sso
836
		 *
837
		 * @since 2.8.0
838
		 *
839
		 * @param string $msg Disclaimer when default WordPress login form is disabled.
840
		 */
841
		$msg = apply_filters( 'jetpack_sso_disclaimer_message', $msg );
842
843
		$message .= sprintf( '<p class="message">%s</p>', $msg );
844
		return $message;
845
	}
846
847
	/**
848
	 * Error message displayed on the login form when the user attempts
849
	 * to post to the login form and it is disabled.
850
	 *
851
	 * @since 2.8
852
	 * @param string $message
853
	 * @param string
854
	 **/
855
	public function error_msg_login_method_not_allowed( $message ) {
856
		$err = __( 'Login method not allowed' , 'jetpack' );
857
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
858
859
		return $message;
860
	}
861
	function cant_find_user( $message ) {
862
		if ( self::match_by_email() ) {
863
			$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' );
864
		} else {
865
			$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' );
866
		}
867
		$err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
868
		$message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
869
		return $message;
870
	}
871
872
	/**
873
	 * Deal with user connections...
874
	 */
875
	function admin_init() {
876
		add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
877
		add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
878
879
		if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
880
			$user = wp_get_current_user();
881
			// Remove the connection on the wpcom end.
882
			self::delete_connection_for_user( $user->ID );
883
			// Clear it locally.
884
			delete_user_meta( $user->ID, 'wpcom_user_id' );
885
			delete_user_meta( $user->ID, 'wpcom_user_data' );
886
			// Forward back to the profile page.
887
			wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
888
		}
889
	}
890
891
	/**
892
	 * Determines if a local user is connected to WordPress.com
893
	 *
894
	 * @since 2.8
895
	 * @param integer $user_id - Local user id
896
	 * @return boolean
897
	 **/
898
	public function is_user_connected( $user_id ) {
899
		return $this->get_user_data( $user_id );
900
	}
901
902
	/**
903
	 * Retrieves a user's WordPress.com data
904
	 *
905
	 * @since 2.8
906
	 * @param integer $user_id - Local user id
907
	 * @return mixed null or stdClass
908
	 **/
909
	public function get_user_data( $user_id ) {
910
		return get_user_meta( $user_id, 'wpcom_user_data', true );
911
	}
912
913
	function edit_profile_fields( $user ) {
914
		wp_enqueue_style( 'genericons' );
915
		?>
916
917
		<h3 id="single-sign-on"><?php _e( 'Single Sign On', 'jetpack' ); ?></h3>
918
		<p><?php _e( 'Connecting with Single Sign On enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
919
920
		<?php if ( $this->is_user_connected( $user->ID ) ) : /* If the user is currently connected... */ ?>
921
			<?php $user_data = $this->get_user_data( $user->ID ); ?>
922
			<table class="form-table jetpack-sso-form-table">
923
				<tbody>
924
					<tr>
925
						<td>
926
							<div class="profile-card">
927
								<?php echo get_avatar( $user_data->email ); ?>
928
								<p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
929
								<p><?php echo esc_html( $user_data->login ); ?></p>
930
								<span class="two_step">
931
									<?php
932
										if ( $user_data->two_step_enabled ) {
933
											?> <p class="enabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Enabled', 'jetpack' ); ?></a></p> <?php
934
										} else {
935
											?> <p class="disabled"><a href="https://wordpress.com/me/security/two-step" target="_blank"><?php _e( 'Two-Step Authentication Disabled', 'jetpack' ); ?></a></p> <?php
936
										}
937
									?>
938
								</span>
939
940
							</div>
941
							<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>
942
						</td>
943
					</tr>
944
				</tbody>
945
			</table>
946
947
			<style>
948
			.jetpack-sso-form-table td {
949
				padding-left: 0;
950
			}
951
952
			.jetpack-sso-form-table .profile-card {
953
				padding: 10px;
954
				background: #fff;
955
				overflow: hidden;
956
				max-width: 400px;
957
				box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.1 );
958
				margin-bottom: 1em;
959
			}
960
961
			.jetpack-sso-form-table .profile-card img {
962
				float: left;
963
				margin-right: 1em;
964
				width: 48px;
965
				height: 48px;
966
			}
967
968
			.jetpack-sso-form-table .profile-card .connected {
969
				float: right;
970
				margin-right: 0.5em;
971
				color: #0a0;
972
			}
973
974
			.jetpack-sso-form-table .profile-card p {
975
				margin-top: 0.7em;
976
				font-size: 1.2em;
977
			}
978
979
			.jetpack-sso-form-table .profile-card .two_step .enabled a {
980
				float: right;
981
				color: #0a0;
982
			}
983
984
			.jetpack-sso-form-table .profile-card .two_step .disabled a {
985
				float: right;
986
				color: red;
987
			}
988
			</style>
989
990
		<?php elseif ( get_current_user_id() == $user->ID && Jetpack::is_user_connected( $user->ID ) ) : ?>
991
992
			<?php echo $this->button( 'state=sso-link-user&_wpnonce=' . wp_create_nonce( 'sso-link-user' ) ); // update ?>
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...
993
994
		<?php else : ?>
995
996
			<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>
997
			<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>
998
999
		<?php endif;
1000
	}
1001
}
1002
1003
Jetpack_SSO::get_instance();
1004