Completed
Push — recurring-payments/minimum-tra... ( a4df71...65f413 )
by
unknown
133:55 queued 127:48
created

Jetpack_SSO::login_enqueue_scripts()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Roles;
4
use Automattic\Jetpack\Status;
5
use Automattic\Jetpack\Tracking;
6
7
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' );
8
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-notices.php' );
9
10
/**
11
 * Module Name: Secure Sign On
12
 * Module Description: Allow users to log in to this site using WordPress.com accounts
13
 * Sort Order: 30
14
 * Recommendation Order: 5
15
 * First Introduced: 2.6
16
 * Requires Connection: Yes
17
 * Auto Activate: No
18
 * Module Tags: Developers
19
 * Feature: Security
20
 * Additional Search Queries: sso, single sign on, login, log in, 2fa, two-factor
21
 */
22
23
class Jetpack_SSO {
24
	static $instance = null;
25
26
	private function __construct() {
27
28
		self::$instance = $this;
29
30
		add_action( 'admin_init',                      array( $this, 'maybe_authorize_user_after_sso' ), 1 );
31
		add_action( 'admin_init',                      array( $this, 'register_settings' ) );
32
		add_action( 'login_init',                      array( $this, 'login_init' ) );
33
		add_action( 'delete_user',                     array( $this, 'delete_connection_for_user' ) );
34
		add_filter( 'jetpack_xmlrpc_methods',          array( $this, 'xmlrpc_methods' ) );
35
		add_action( 'init',                            array( $this, 'maybe_logout_user' ), 5 );
36
		add_action( 'jetpack_modules_loaded',          array( $this, 'module_configure_button' ) );
37
		add_action( 'login_form_logout',               array( $this, 'store_wpcom_profile_cookies_on_logout' ) );
38
		add_action( 'jetpack_unlinked_user',           array( $this, 'delete_connection_for_user') );
39
		add_action( 'wp_login',                        array( 'Jetpack_SSO', 'clear_cookies_after_login' ) );
40
		add_action( 'jetpack_jitm_received_envelopes', array( $this, 'inject_sso_jitm' ), 10, 2 );
41
42
		// Adding this action so that on login_init, the action won't be sanitized out of the $action global.
43
		add_action( 'login_form_jetpack-sso', '__return_true' );
44
	}
45
46
	/**
47
	 * Returns the single instance of the Jetpack_SSO object
48
	 *
49
	 * @since 2.8
50
	 * @return Jetpack_SSO
51
	 **/
52
	public static function get_instance() {
53
		if ( ! is_null( self::$instance ) ) {
54
			return self::$instance;
55
		}
56
57
		return self::$instance = new Jetpack_SSO;
58
	}
59
60
	/**
61
	 * Add configure button and functionality to the module card on the Jetpack screen
62
	 **/
63
	public static function module_configure_button() {
64
		Jetpack::enable_module_configurable( __FILE__ );
65
	}
66
67
	/**
68
	 * If jetpack_force_logout == 1 in current user meta the user will be forced
69
	 * to logout and reauthenticate with the site.
70
	 **/
71
	public function maybe_logout_user() {
72
		global $current_user;
73
74
		if ( 1 == $current_user->jetpack_force_logout ) {
75
			delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
76
			self::delete_connection_for_user( $current_user->ID );
77
			wp_logout();
78
			wp_safe_redirect( wp_login_url() );
79
			exit;
80
		}
81
	}
82
83
	/**
84
	 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
85
	 *
86
	 * @param array $methods
87
	 * @return array
88
	 **/
89
	public function xmlrpc_methods( $methods ) {
90
		$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
91
		return $methods;
92
	}
93
94
	/**
95
	 * Marks a user's profile for disconnect from WordPress.com and forces a logout
96
	 * the next time the user visits the site.
97
	 **/
98
	public function xmlrpc_user_disconnect( $user_id ) {
99
		$user_query = new WP_User_Query(
100
			array(
101
				'meta_key' => 'wpcom_user_id',
102
				'meta_value' => $user_id,
103
			)
104
		);
105
		$user = $user_query->get_results();
106
		$user = $user[0];
107
108
		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...
109
			$user = wp_set_current_user( $user->ID );
110
			update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
111
			self::delete_connection_for_user( $user->ID );
112
			return true;
113
		}
114
		return false;
115
	}
116
117
	/**
118
	 * Enqueues scripts and styles necessary for SSO login.
119
	 */
120
	public function login_enqueue_scripts() {
121
		global $action;
122
123
		if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
124
			return;
125
		}
126
127
		if ( is_rtl() ) {
128
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login-rtl.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
129
		} else {
130
			wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION );
131
		}
132
133
		wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION );
134
	}
135
136
	/**
137
	 * Adds Jetpack SSO classes to login body
138
	 *
139
	 * @param  array $classes Array of classes to add to body tag
140
	 * @return array          Array of classes to add to body tag
141
	 */
142
	public function login_body_class( $classes ) {
143
		global $action;
144
145
		if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
146
			return $classes;
147
		}
148
149
		// Always add the jetpack-sso class so that we can add SSO specific styling even when the SSO form isn't being displayed.
150
		$classes[] = 'jetpack-sso';
151
152
		if ( ! ( new Status() )->is_staging_site() ) {
153
			/**
154
			 * Should we show the SSO login form?
155
			 *
156
			 * $_GET['jetpack-sso-default-form'] is used to provide a fallback in case JavaScript is not enabled.
157
			 *
158
			 * The default_to_sso_login() method allows us to dynamically decide whether we show the SSO login form or not.
159
			 * The SSO module uses the method to display the default login form if we can not find a user to log in via SSO.
160
			 * But, the method could be filtered by a site admin to always show the default login form if that is preferred.
161
			 */
162
			if ( empty( $_GET['jetpack-sso-show-default-form'] ) && Jetpack_SSO_Helpers::show_sso_login() ) {
163
				$classes[] = 'jetpack-sso-form-display';
164
			}
165
		}
166
167
		return $classes;
168
	}
169
170
	public function print_inline_admin_css() {
171
		?>
172
			<style>
173
				.jetpack-sso .message {
174
					margin-top: 20px;
175
				}
176
177
				.jetpack-sso #login .message:first-child,
178
				.jetpack-sso #login h1 + .message {
179
					margin-top: 0;
180
				}
181
			</style>
182
		<?php
183
	}
184
185
	/**
186
	 * Adds settings fields to Settings > General > Secure Sign On that allows users to
187
	 * turn off the login form on wp-login.php
188
	 *
189
	 * @since 2.7
190
	 **/
191
	public function register_settings() {
192
193
		add_settings_section(
194
			'jetpack_sso_settings',
195
			__( 'Secure Sign On' , 'jetpack' ),
196
			'__return_false',
197
			'jetpack-sso'
198
		);
199
200
		/*
201
		 * Settings > General > Secure Sign On
202
		 * Require two step authentication
203
		 */
204
		register_setting(
205
			'jetpack-sso',
206
			'jetpack_sso_require_two_step',
207
			array( $this, 'validate_jetpack_sso_require_two_step' )
208
		);
209
210
		add_settings_field(
211
			'jetpack_sso_require_two_step',
212
			'', // __( 'Require Two-Step Authentication' , 'jetpack' ),
213
			array( $this, 'render_require_two_step' ),
214
			'jetpack-sso',
215
			'jetpack_sso_settings'
216
		);
217
218
		/*
219
		 * Settings > General > Secure Sign On
220
		 */
221
		register_setting(
222
			'jetpack-sso',
223
			'jetpack_sso_match_by_email',
224
			array( $this, 'validate_jetpack_sso_match_by_email' )
225
		);
226
227
		add_settings_field(
228
			'jetpack_sso_match_by_email',
229
			'', // __( 'Match by Email' , 'jetpack' ),
230
			array( $this, 'render_match_by_email' ),
231
			'jetpack-sso',
232
			'jetpack_sso_settings'
233
		);
234
	}
235
236
	/**
237
	 * Builds the display for the checkbox allowing user to require two step
238
	 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
239
	 *
240
	 * @since 2.7
241
	 **/
242
	public function render_require_two_step() {
243
		?>
244
		<label>
245
			<input
246
				type="checkbox"
247
				name="jetpack_sso_require_two_step"
248
				<?php checked( Jetpack_SSO_Helpers::is_two_step_required() ); ?>
249
				<?php disabled( Jetpack_SSO_Helpers::is_require_two_step_checkbox_disabled() ); ?>
250
			>
251
			<?php esc_html_e( 'Require Two-Step Authentication' , 'jetpack' ); ?>
252
		</label>
253
		<?php
254
	}
255
256
	/**
257
	 * Validate the require  two step checkbox in Settings > General
258
	 *
259
	 * @since 2.7
260
	 * @return boolean
261
	 **/
262
	public function validate_jetpack_sso_require_two_step( $input ) {
263
		return ( ! empty( $input ) ) ? 1 : 0;
264
	}
265
266
	/**
267
	 * Builds the display for the checkbox allowing the user to allow matching logins by email
268
	 * Displays in Settings > General
269
	 *
270
	 * @since 2.9
271
	 **/
272
	public function render_match_by_email() {
273
		?>
274
			<label>
275
				<input
276
					type="checkbox"
277
					name="jetpack_sso_match_by_email"
278
					<?php checked( Jetpack_SSO_Helpers::match_by_email() ); ?>
279
					<?php disabled( Jetpack_SSO_Helpers::is_match_by_email_checkbox_disabled() ); ?>
280
				>
281
				<?php esc_html_e( 'Match by Email', 'jetpack' ); ?>
282
			</label>
283
		<?php
284
	}
285
286
	/**
287
	 * Validate the match by email check in Settings > General
288
	 *
289
	 * @since 2.9
290
	 * @return boolean
291
	 **/
292
	public function validate_jetpack_sso_match_by_email( $input ) {
293
		return ( ! empty( $input ) ) ? 1 : 0;
294
	}
295
296
	/**
297
	 * Checks to determine if the user wants to login on wp-login
298
	 *
299
	 * This function mostly exists to cover the exceptions to login
300
	 * that may exist as other parameters to $_GET[action] as $_GET[action]
301
	 * does not have to exist. By default WordPress assumes login if an action
302
	 * is not set, however this may not be true, as in the case of logout
303
	 * where $_GET[loggedout] is instead set
304
	 *
305
	 * @return boolean
306
	 **/
307
	private function wants_to_login() {
308
		$wants_to_login = false;
309
310
		// Cover default WordPress behavior
311
		$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login';
312
313
		// And now the exceptions
314
		$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
315
316
		if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
317
			$wants_to_login = true;
318
		}
319
320
		return $wants_to_login;
321
	}
322
323
	function login_init() {
324
		global $action;
325
326
		$tracking = new Tracking();
327
328
		if ( Jetpack_SSO_Helpers::should_hide_login_form() ) {
329
			/**
330
			 * Since the default authenticate filters fire at priority 20 for checking username and password,
331
			 * let's fire at priority 30. wp_authenticate_spam_check is fired at priority 99, but since we return a
332
			 * WP_Error in disable_default_login_form, then we won't trigger spam processing logic.
333
			 */
334
			add_filter( 'authenticate', array( 'Jetpack_SSO_Notices', 'disable_default_login_form' ), 30 );
335
336
			/**
337
			 * Filter the display of the disclaimer message appearing when default WordPress login form is disabled.
338
			 *
339
			 * @module sso
340
			 *
341
			 * @since 2.8.0
342
			 *
343
			 * @param bool true Should the disclaimer be displayed. Default to true.
344
			 */
345
			$display_sso_disclaimer = apply_filters( 'jetpack_sso_display_disclaimer', true );
346
			if ( $display_sso_disclaimer ) {
347
				add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'msg_login_by_jetpack' ) );
348
			}
349
		}
350
351
		 if ( 'jetpack-sso' === $action ) {
352
			if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
353
				$this->handle_login();
354
				$this->display_sso_login_form();
355
			} else {
356
				if ( ( new Status() )->is_staging_site() ) {
357
					add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) );
358
				} else {
359
					// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
360
					add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
361
					$reauth = ! empty( $_GET['force_reauth'] );
362
					$sso_url = $this->get_sso_url_or_die( $reauth );
363
364
					// Is this our first SSO Login. Set an option.
365
					if ( ! Jetpack_Options::get_option( 'sso_first_login' ) ) {
366
						Jetpack_options::update_option( 'sso_first_login', true );
367
					}
368
369
					$tracking->record_user_event( 'sso_login_redirect_success' );
370
					wp_safe_redirect( $sso_url );
371
					exit;
372
				}
373
			}
374
		} else if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) {
375
376
			// Save cookies so we can handle redirects after SSO
377
			$this->save_cookies();
378
379
			/**
380
			 * Check to see if the site admin wants to automagically forward the user
381
			 * to the WordPress.com login page AND  that the request to wp-login.php
382
			 * is not something other than login (Like logout!)
383
			 */
384
			if ( Jetpack_SSO_Helpers::bypass_login_forward_wpcom() && $this->wants_to_login() ) {
385
				add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
386
				$reauth = ! empty( $_GET['force_reauth'] );
387
				$sso_url = $this->get_sso_url_or_die( $reauth );
388
				$tracking->record_user_event( 'sso_login_redirect_bypass_success' );
389
				wp_safe_redirect( $sso_url );
390
				exit;
391
			}
392
393
			$this->display_sso_login_form();
394
		}
395
	}
396
397
	/**
398
	 * Ensures that we can get a nonce from WordPress.com via XML-RPC before setting
399
	 * up the hooks required to display the SSO form.
400
	 */
401
	public function display_sso_login_form() {
402
		add_filter( 'login_body_class', array( $this, 'login_body_class' ) );
403
		add_action( 'login_head',       array( $this, 'print_inline_admin_css' ) );
404
405
		if ( ( new Status() )->is_staging_site() ) {
406
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) );
407
			return;
408
		}
409
410
		$sso_nonce = self::request_initial_nonce();
411
		if ( is_wp_error( $sso_nonce ) ) {
412
			return;
413
		}
414
415
		add_action( 'login_form',            array( $this, 'login_form' ) );
416
		add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) );
417
	}
418
419
	/**
420
	 * Conditionally save the redirect_to url as a cookie.
421
	 *
422
	 * @since 4.6.0 Renamed to save_cookies from maybe_save_redirect_cookies
423
	 */
424
	public static function save_cookies() {
425
		if ( headers_sent() ) {
426
			return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'headers_sent'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
427
		}
428
429
		setcookie(
430
			'jetpack_sso_original_request',
431
			esc_url_raw( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ),
432
			time() + HOUR_IN_SECONDS,
433
			COOKIEPATH,
434
			COOKIE_DOMAIN,
435
			is_ssl(),
436
			true
437
		);
438
439
		if ( ! empty( $_GET['redirect_to'] ) ) {
440
			// If we have something to redirect to
441
			$url = esc_url_raw( $_GET['redirect_to'] );
442
			setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
443
		} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
444
			// Otherwise, if it's already set, purge it.
445
			setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
446
		}
447
	}
448
449
	/**
450
	 * Outputs the Jetpack SSO button and description as well as the toggle link
451
	 * for switching between Jetpack SSO and default login.
452
	 */
453
	function login_form() {
454
		$site_name = get_bloginfo( 'name' );
455
		if ( ! $site_name ) {
456
			$site_name = get_bloginfo( 'url' );
457
		}
458
459
		$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] )
460
			? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ]
461
			: false;
462
		$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] )
463
			? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ]
464
			: false;
465
466
		?>
467
		<div id="jetpack-sso-wrap">
468
			<?php if ( $display_name && $gravatar ) : ?>
469
				<div id="jetpack-sso-wrap__user">
470
					<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" />
471
472
					<h2>
473
						<?php
474
							echo wp_kses(
475
								sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ),
476
								array( 'span' => true )
477
							);
478
						?>
479
					</h2>
480
				</div>
481
482
			<?php endif; ?>
483
484
485
			<div id="jetpack-sso-wrap__action">
486
				<?php echo $this->build_sso_button( array(), 'is_primary' ); ?>
0 ignored issues
show
Documentation introduced by
'is_primary' is of type string, but the function expects a boolean.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
487
488
				<?php if ( $display_name && $gravatar ) : ?>
489
					<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>">
490
						<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?>
491
					</a>
492
				<?php else : ?>
493
					<p>
494
						<?php
495
							echo esc_html(
496
								sprintf(
497
									__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ),
498
									esc_html( $site_name )
499
								)
500
							);
501
						?>
502
					</p>
503
				<?php endif; ?>
504
			</div>
505
506
			<?php if ( ! Jetpack_SSO_Helpers::should_hide_login_form() ) : ?>
507
				<div class="jetpack-sso-or">
508
					<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span>
509
				</div>
510
511
				<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '1' ) ); ?>" class="jetpack-sso-toggle wpcom">
512
					<?php
513
						esc_html_e( 'Log in with username and password', 'jetpack' )
514
					?>
515
				</a>
516
517
				<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '0' ) ); ?>" class="jetpack-sso-toggle default">
518
					<?php
519
						esc_html_e( 'Log in with WordPress.com', 'jetpack' )
520
					?>
521
				</a>
522
			<?php endif; ?>
523
		</div>
524
		<?php
525
	}
526
527
	/**
528
	 * Clear the cookies that store the profile information for the last
529
	 * WPCOM user to connect.
530
	 */
531
	static function clear_wpcom_profile_cookies() {
532 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) {
533
			setcookie(
534
				'jetpack_sso_wpcom_name_' . COOKIEHASH,
535
				' ',
536
				time() - YEAR_IN_SECONDS,
537
				COOKIEPATH,
538
				COOKIE_DOMAIN,
539
				is_ssl()
540
			);
541
		}
542
543 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) {
544
			setcookie(
545
				'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
546
				' ',
547
				time() - YEAR_IN_SECONDS,
548
				COOKIEPATH,
549
				COOKIE_DOMAIN,
550
				is_ssl()
551
			);
552
		}
553
	}
554
555
	/**
556
	 * Clear cookies that are no longer needed once the user has logged in.
557
	 *
558
	 * @since 4.8.0
559
	 */
560
	static function clear_cookies_after_login() {
561
		self::clear_wpcom_profile_cookies();
562 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_nonce' ] ) ) {
563
			setcookie(
564
				'jetpack_sso_nonce',
565
				' ',
566
				time() - YEAR_IN_SECONDS,
567
				COOKIEPATH,
568
				COOKIE_DOMAIN,
569
				is_ssl()
570
			);
571
		}
572
573 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_original_request' ] ) ) {
574
			setcookie(
575
				'jetpack_sso_original_request',
576
				' ',
577
				time() - YEAR_IN_SECONDS,
578
				COOKIEPATH,
579
				COOKIE_DOMAIN,
580
				is_ssl()
581
			);
582
		}
583
584 View Code Duplication
		if ( isset( $_COOKIE[ 'jetpack_sso_redirect_to' ] ) ) {
585
			setcookie(
586
				'jetpack_sso_redirect_to',
587
				' ',
588
				time() - YEAR_IN_SECONDS,
589
				COOKIEPATH,
590
				COOKIE_DOMAIN,
591
				is_ssl()
592
			);
593
		}
594
	}
595
596
	static function delete_connection_for_user( $user_id ) {
597
		if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
598
			return;
599
		}
600
601
		$xml = new Jetpack_IXR_Client( array(
602
			'wpcom_user_id' => $user_id,
603
		) );
604
		$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
605
606
		if ( $xml->isError() ) {
607
			return false;
608
		}
609
610
		// Clean up local data stored for SSO
611
		delete_user_meta( $user_id, 'wpcom_user_id' );
612
		delete_user_meta( $user_id, 'wpcom_user_data'  );
613
		self::clear_wpcom_profile_cookies();
614
615
		return $xml->getResponse();
616
	}
617
618
	static function request_initial_nonce() {
619
		$nonce = ! empty( $_COOKIE[ 'jetpack_sso_nonce' ] )
620
			? $_COOKIE[ 'jetpack_sso_nonce' ]
621
			: false;
622
623
		if ( ! $nonce ) {
624
			$xml = new Jetpack_IXR_Client( array(
625
				'user_id' => get_current_user_id(),
626
			) );
627
			$xml->query( 'jetpack.sso.requestNonce' );
628
629
			if ( $xml->isError() ) {
630
				return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with $xml->getErrorCode().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
631
			}
632
633
			$nonce = $xml->getResponse();
634
635
			setcookie(
636
				'jetpack_sso_nonce',
637
				$nonce,
638
				time() + ( 10 * MINUTE_IN_SECONDS ),
639
				COOKIEPATH,
640
				COOKIE_DOMAIN,
641
				is_ssl()
642
			);
643
		}
644
645
		return sanitize_key( $nonce );
646
	}
647
648
	/**
649
	 * The function that actually handles the login!
650
	 */
651
	function handle_login() {
652
		$wpcom_nonce   = sanitize_key( $_GET['sso_nonce'] );
653
		$wpcom_user_id = (int) $_GET['user_id'];
654
655
		$xml = new Jetpack_IXR_Client( array(
656
			'user_id' => get_current_user_id(),
657
		) );
658
		$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
659
660
		$user_data = $xml->isError() ? false : $xml->getResponse();
661
		if ( empty( $user_data ) ) {
662
			add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
663
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_invalid_response_data' ) );
664
			return;
665
		}
666
667
		$user_data = (object) $user_data;
668
		$user = null;
669
670
		/**
671
		 * Fires before Jetpack's SSO modifies the log in form.
672
		 *
673
		 * @module sso
674
		 *
675
		 * @since 2.6.0
676
		 *
677
		 * @param object $user_data WordPress.com User information.
678
		 */
679
		do_action( 'jetpack_sso_pre_handle_login', $user_data );
680
681
		$tracking = new Tracking();
682
683
		if ( Jetpack_SSO_Helpers::is_two_step_required() && 0 === (int) $user_data->two_step_enabled ) {
684
			$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...
685
686
			$tracking->record_user_event( 'sso_login_failed', array(
687
				'error_message' => 'error_msg_enable_two_step'
688
			) );
689
690
			$error = new WP_Error( 'two_step_required', __( 'You must have Two-Step Authentication enabled on your WordPress.com account.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'two_step_required'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
691
692
			/** This filter is documented in core/src/wp-includes/pluggable.php */
693
			do_action( 'wp_login_failed', $user_data->login, $error );
694
			add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_enable_two_step' ) );
695
			return;
696
		}
697
698
		$user_found_with = '';
699
		if ( empty( $user ) && isset( $user_data->external_user_id ) ) {
700
			$user_found_with = 'external_user_id';
701
			$user = get_user_by( 'id', intval( $user_data->external_user_id ) );
702
			if ( $user ) {
703
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
704
			}
705
		}
706
707
		// If we don't have one by wpcom_user_id, try by the email?
708
		if ( empty( $user ) && Jetpack_SSO_Helpers::match_by_email() ) {
709
			$user_found_with = 'match_by_email';
710
			$user = get_user_by( 'email', $user_data->email );
711
			if ( $user ) {
712
				update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
713
			}
714
		}
715
716
		// If we've still got nothing, create the user.
717
		$new_user_override_role = false;
718
		if ( empty( $user ) && ( get_option( 'users_can_register' ) || ( $new_user_override_role = Jetpack_SSO_Helpers::new_user_override( $user_data ) ) ) ) {
719
			/**
720
			 * If not matching by email we still need to verify the email does not exist
721
			 * or this blows up
722
			 *
723
			 * If match_by_email is true, we know the email doesn't exist, as it would have
724
			 * been found in the first pass.  If get_user_by( 'email' ) doesn't find the
725
			 * user, then we know that email is unused, so it's safe to add.
726
			 */
727
			if ( Jetpack_SSO_Helpers::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
728
729
				if ( $new_user_override_role ) {
730
					$user_data->role = $new_user_override_role;
731
				}
732
733
				$user = Jetpack_SSO_Helpers::generate_user( $user_data );
734
				if ( ! $user ) {
735
					$tracking->record_user_event( 'sso_login_failed', array(
736
						'error_message' => 'could_not_create_username'
737
					) );
738
					add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_unable_to_create_user' ) );
739
					return;
740
				}
741
742
				$user_found_with = $new_user_override_role
743
					? 'user_created_new_user_override'
744
					: 'user_created_users_can_register';
745
			} else {
746
				$tracking->record_user_event( 'sso_login_failed', array(
747
					'error_message' => 'error_msg_email_already_exists'
748
				) );
749
750
				$this->user_data = $user_data;
751
				add_action( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_email_already_exists' ) );
752
				return;
753
			}
754
		}
755
756
		/**
757
		 * Fires after we got login information from WordPress.com.
758
		 *
759
		 * @module sso
760
		 *
761
		 * @since 2.6.0
762
		 *
763
		 * @param array  $user      Local User information.
764
		 * @param object $user_data WordPress.com User Login information.
765
		 */
766
		do_action( 'jetpack_sso_handle_login', $user, $user_data );
767
768
		if ( $user ) {
769
			// Cache the user's details, so we can present it back to them on their user screen
770
			update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
771
772
			add_filter( 'auth_cookie_expiration',    array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) );
773
			wp_set_auth_cookie( $user->ID, true );
774
			remove_filter( 'auth_cookie_expiration', array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) );
775
776
			/** This filter is documented in core/src/wp-includes/user.php */
777
			do_action( 'wp_login', $user->user_login, $user );
778
779
			wp_set_current_user( $user->ID );
780
781
			$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
782
			$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
783
784
			// If we have a saved redirect to request in a cookie
785
			if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
786
				// Set that as the requested redirect to
787
				$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
788
			}
789
790
			$json_api_auth_environment = Jetpack_SSO_Helpers::get_json_api_auth_environment();
791
792
			$is_json_api_auth  = ! empty( $json_api_auth_environment );
793
			$is_user_connected = Jetpack::is_user_connected( $user->ID );
794
			$roles             = new Roles();
795
			$tracking->record_user_event( 'sso_user_logged_in', array(
796
				'user_found_with'  => $user_found_with,
797
				'user_connected'   => (bool) $is_user_connected,
798
				'user_role'        => $roles->translate_current_user_to_role(),
799
				'is_json_api_auth' => (bool) $is_json_api_auth,
800
			) );
801
802
			if ( $is_json_api_auth ) {
803
				Jetpack::init()->verify_json_api_authorization_request( $json_api_auth_environment );
804
				Jetpack::init()->store_json_api_authorization_token( $user->user_login, $user );
805
806
			} else if ( ! $is_user_connected ) {
807
				wp_safe_redirect(
808
					add_query_arg(
809
						array(
810
							'redirect_to'               => $redirect_to,
811
							'request_redirect_to'       => $_request_redirect_to,
812
							'calypso_env'               => Jetpack::get_calypso_env(),
813
							'jetpack-sso-auth-redirect' => '1',
814
						),
815
						admin_url()
816
					)
817
				);
818
				exit;
819
			}
820
821
			add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
822
			wp_safe_redirect(
823
				/** This filter is documented in core/src/wp-login.php */
824
				apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user )
825
			);
826
			exit;
827
		}
828
829
		add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' );
830
831
		$tracking->record_user_event( 'sso_login_failed', array(
832
			'error_message' => 'cant_find_user'
833
		) );
834
835
		$this->user_data = $user_data;
836
837
		$error = new WP_Error( 'account_not_found', __( 'Account not found. If you already have an account, make sure you have connected to WordPress.com.', 'jetpack' ) );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'account_not_found'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
838
839
		/** This filter is documented in core/src/wp-includes/pluggable.php */
840
		do_action( 'wp_login_failed', $user_data->login, $error );
841
		add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'cant_find_user' ) );
842
	}
843
844
	static function profile_page_url() {
845
		return admin_url( 'profile.php' );
846
	}
847
848
	/**
849
	 * Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page.
850
	 *
851
	 * @param  array   $args       An array of arguments to add to the SSO URL.
852
	 * @param  boolean $is_primary Should the button have the `button-primary` class?
853
	 * @return string              Returns the HTML markup for the button.
854
	 */
855
	function build_sso_button( $args = array(), $is_primary = false ) {
856
		$url = $this->build_sso_button_url( $args );
857
		$classes = $is_primary
858
			? 'jetpack-sso button button-primary'
859
			: 'jetpack-sso button';
860
861
		return sprintf(
862
			'<a rel="nofollow" href="%1$s" class="%2$s"><span>%3$s %4$s</span></a>',
863
			esc_url( $url ),
864
			$classes,
865
			'<span class="genericon genericon-wordpress"></span>',
866
			esc_html__( 'Log in with WordPress.com', 'jetpack' )
867
		);
868
	}
869
870
	/**
871
	 * Builds a URL with `jetpack-sso` action and option args which is used to setup SSO.
872
	 *
873
	 * @param  array  $args An array of arguments to add to the SSO URL.
874
	 * @return string       The URL used for SSO.
875
	 */
876
	function build_sso_button_url( $args = array() ) {
877
		$defaults = array(
878
			'action'  => 'jetpack-sso',
879
		);
880
881
		$args = wp_parse_args( $args, $defaults );
882
883
		if ( ! empty( $_GET['redirect_to'] ) ) {
884
			$args['redirect_to'] = urlencode( esc_url_raw( $_GET['redirect_to'] ) );
885
		}
886
887
		return add_query_arg( $args, wp_login_url() );
888
	}
889
890
	/**
891
	 * Retrieves a WordPress.com SSO URL with appropriate query parameters or dies.
892
	 *
893
	 * @param  boolean  $reauth  Should the user be forced to reauthenticate on WordPress.com?
894
	 * @param  array    $args    Optional query parameters.
895
	 * @return string            The WordPress.com SSO URL.
896
	 */
897
	function get_sso_url_or_die( $reauth = false, $args = array() ) {
898
		if ( empty( $reauth ) ) {
899
			$sso_redirect = $this->build_sso_url( $args );
900
		} else {
901
			self::clear_wpcom_profile_cookies();
902
			$sso_redirect = $this->build_reauth_and_sso_url( $args );
903
		}
904
905
		// If there was an error retrieving the SSO URL, then error.
906
		if ( is_wp_error( $sso_redirect ) ) {
907
			$error_message = sanitize_text_field(
908
				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...
909
			);
910
			$tracking = new Tracking();
911
			$tracking->record_user_event( 'sso_login_redirect_failed', array(
912
				'error_message' => $error_message
913
			) );
914
			wp_die( $error_message );
915
		}
916
917
		return $sso_redirect;
918
	}
919
920
	/**
921
	 * Build WordPress.com SSO URL with appropriate query parameters.
922
	 *
923
	 * @param  array  $args Optional query parameters.
924
	 * @return string       WordPress.com SSO URL
925
	 */
926
	function build_sso_url( $args = array() ) {
927
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
928
		$defaults = array(
929
			'action'       => 'jetpack-sso',
930
			'site_id'      => Jetpack_Options::get_option( 'id' ),
931
			'sso_nonce'    => $sso_nonce,
932
			'calypso_auth' => '1',
933
		);
934
935
		$args = wp_parse_args( $args, $defaults );
936
937
		if ( is_wp_error( $args['sso_nonce'] ) ) {
938
			return $args['sso_nonce'];
939
		}
940
941
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
942
	}
943
944
	/**
945
	 * Build WordPress.com SSO URL with appropriate query parameters,
946
	 * including the parameters necessary to force the user to reauthenticate
947
	 * on WordPress.com.
948
	 *
949
	 * @param  array  $args Optional query parameters.
950
	 * @return string       WordPress.com SSO URL
951
	 */
952
	function build_reauth_and_sso_url( $args = array() ) {
953
		$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce();
954
		$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) );
955
956
		if ( is_wp_error( $redirect ) ) {
957
			return $redirect;
958
		}
959
960
		$defaults = array(
961
			'action'       => 'jetpack-sso',
962
			'site_id'      => Jetpack_Options::get_option( 'id' ),
963
			'sso_nonce'    => $sso_nonce,
964
			'reauth'       => '1',
965
			'redirect_to'  => urlencode( $redirect ),
966
			'calypso_auth' => '1',
967
		);
968
969
		$args = wp_parse_args( $args, $defaults );
970
971
		if ( is_wp_error( $args['sso_nonce'] ) ) {
972
			return $args['sso_nonce'];
973
		}
974
975
		return add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
976
	}
977
978
	/**
979
	 * Determines local user associated with a given WordPress.com user ID.
980
	 *
981
	 * @since 2.6.0
982
	 *
983
	 * @param int $wpcom_user_id User ID from WordPress.com
984
	 * @return object Local user object if found, null if not.
985
	 */
986
	static function get_user_by_wpcom_id( $wpcom_user_id ) {
987
		$user_query = new WP_User_Query( array(
988
			'meta_key'   => 'wpcom_user_id',
989
			'meta_value' => intval( $wpcom_user_id ),
990
			'number'     => 1,
991
		) );
992
993
		$users = $user_query->get_results();
994
		return $users ? array_shift( $users ) : null;
995
	}
996
997
	/**
998
	 * When jetpack-sso-auth-redirect query parameter is set, will redirect user to
999
	 * WordPress.com authorization flow.
1000
	 *
1001
	 * We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url
1002
	 * calls menu_page_url() which doesn't work properly until admin menus are registered.
1003
	 */
1004
	function maybe_authorize_user_after_sso() {
1005
		if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) {
1006
			return;
1007
		}
1008
1009
		$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url();
1010
		$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to;
1011
1012
		/** This filter is documented in core/src/wp-login.php */
1013
		$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() );
1014
1015
		/**
1016
		 * Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(),
1017
		 * let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url().
1018
		 */
1019
		$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth );
1020
		$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() );
1021
1022
		/**
1023
		 * Return the raw connect URL with our redirect and attribute connection to SSO.
1024
		 */
1025
		$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' );
1026
1027
		add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) );
1028
		wp_safe_redirect( $connect_url );
1029
		exit;
1030
	}
1031
1032
	/**
1033
	 * Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are
1034
	 * stored when the user logs out, and then deleted when the user logs in.
1035
	 */
1036
	function store_wpcom_profile_cookies_on_logout() {
1037
		if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) {
1038
			return;
1039
		}
1040
1041
		$user_data = $this->get_user_data( get_current_user_id() );
1042
		if ( ! $user_data ) {
1043
			return;
1044
		}
1045
1046
		setcookie(
1047
			'jetpack_sso_wpcom_name_' . COOKIEHASH,
1048
			$user_data->display_name,
1049
			time() + WEEK_IN_SECONDS,
1050
			COOKIEPATH,
1051
			COOKIE_DOMAIN,
1052
			is_ssl()
1053
		);
1054
1055
		setcookie(
1056
			'jetpack_sso_wpcom_gravatar_' . COOKIEHASH,
1057
			get_avatar_url(
1058
				$user_data->email,
1059
				array( 'size' => 144, 'default' => 'mystery' )
1060
			),
1061
			time() + WEEK_IN_SECONDS,
1062
			COOKIEPATH,
1063
			COOKIE_DOMAIN,
1064
			is_ssl()
1065
		);
1066
	}
1067
1068
	/**
1069
	 * Determines if a local user is connected to WordPress.com
1070
	 *
1071
	 * @since 2.8
1072
	 * @param integer $user_id - Local user id
1073
	 * @return boolean
1074
	 **/
1075
	public function is_user_connected( $user_id ) {
1076
		return $this->get_user_data( $user_id );
1077
	}
1078
1079
	/**
1080
	 * Retrieves a user's WordPress.com data
1081
	 *
1082
	 * @since 2.8
1083
	 * @param integer $user_id - Local user id
1084
	 * @return mixed null or stdClass
1085
	 **/
1086
	public function get_user_data( $user_id ) {
1087
		return get_user_meta( $user_id, 'wpcom_user_data', true );
1088
	}
1089
1090
	/**
1091
	 * Mark SSO as discovered when an SSO JITM is viewed.
1092
	 *
1093
	 * @since 6.9.0
1094
	 *
1095
	 * @param array  $envelopes    Array of JITM messages received after API call.
1096
	 * @param string $message_path The message path to ask for.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $message_path not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1097
	 *
1098
	 * @return array $envelopes New array of JITM messages. May now contain only one message, about SSO.
1099
	 */
1100
	public function inject_sso_jitm( $envelopes, $message_path = null) {
1101
		/*
1102
		 * Bail early if:
1103
		 * - the request does not originate from wp-admin main dashboard.
1104
		 * - that's not the first time the user uses SSO.
1105
		 */
1106
		if (
1107
			'wp:dashboard:admin_notices' !== $message_path
1108
			|| true !== Jetpack_Options::get_option( 'sso_first_login' )
1109
		) {
1110
			return $envelopes;
1111
		}
1112
1113
		// Update our option to mark that SSO was discovered.
1114
		Jetpack_Options::update_option( 'sso_first_login', false );
1115
1116
		return $this->prepare_sso_first_login_jitm();
1117
	}
1118
1119
	/**
1120
	 * Prepare JITM array for new SSO users
1121
	 *
1122
	 * @since 6.9.0
1123
	 *
1124
	 * @return array $sso_first_login_jitm array containting one object of information about our message.
1125
	 */
1126
	private function prepare_sso_first_login_jitm() {
1127
		// Build our custom SSO JITM.
1128
		$discover_sso_message = array(
1129
			'content'         => array(
1130
				'message'     => esc_html__( "You've successfully signed in with WordPress.com Secure Sign On!", 'jetpack' ),
1131
				'icon'        => 'jetpack',
1132
				'list'        => array(),
1133
				'description' => esc_html__( 'Interested in learning more about how Secure Sign On keeps your site safer?', 'jetpack' ),
1134
				'classes'     => '',
1135
			),
1136
			'CTA'             => array(
1137
				'message'   => esc_html__( 'Learn More', 'jetpack' ),
1138
				'hook'      => '',
1139
				'newWindow' => true,
1140
				'primary'   => true,
1141
			),
1142
			'template'        => 'default',
1143
			'ttl'             => 300,
1144
			'id'              => 'sso_discover',
1145
			'feature_class'   => 'sso',
1146
			'expires'         => 3628800,
1147
			'max_dismissal'   => 1,
1148
			'activate_module' => null,
1149
		);
1150
1151
		return array( json_decode( json_encode( $discover_sso_message ) ) );
1152
	}
1153
}
1154
1155
Jetpack_SSO::get_instance();
1156