PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 3.6.4
Jetpack – WP Security, Backup, Speed, & Growth v3.6.4
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / sso.php

sso.php in Jetpack – WP Security, Backup, Speed, & Growth 3.6.4, at modules/sso.php

991 lines 30.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Module Name: Jetpack Single Sign On
5 * Module Description: Allow your users to log in using their WordPress.com accounts.
6 * Jumpstart Description: lets you login 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
14 */
15
16 class Jetpack_SSO {
17 static $instance = null;
18
19 private function __construct() {
20
21 self::$instance = $this;
22
23 add_action( 'admin_init', array( $this, 'admin_init' ) );
24 add_action( 'admin_init', array( $this, 'register_settings' ) );
25 add_action( 'login_init', array( $this, 'login_init' ) );
26 add_action( 'delete_user', array( $this, 'delete_connection_for_user' ) );
27 add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) );
28 add_action( 'init', array( $this, 'maybe_logout_user' ), 5 );
29 add_action( 'jetpack_modules_loaded', array( $this, 'module_configure_button' ) );
30
31 // Adding this action so that on login_init, the action won't be sanitized out of the $action global.
32 add_action( 'login_form_jetpack-sso', '__return_true' );
33
34 if ( $this->should_hide_login_form() && apply_filters( 'jetpack_sso_display_disclaimer', true ) ) {
35 add_action( 'login_message', array( $this, 'msg_login_by_jetpack' ) );
36 }
37 }
38
39 /**
40 * Returns the single instance of the Jetpack_SSO object
41 *
42 * @since 2.8
43 * @return Jetpack_SSO
44 **/
45 public static function get_instance() {
46 if( !is_null( self::$instance ) )
47 return self::$instance;
48
49 return self::$instance = new Jetpack_SSO;
50 }
51
52 /**
53 * Add configure button and functionality to the module card on the Jetpack screen
54 **/
55 public static function module_configure_button() {
56 Jetpack::enable_module_configurable( __FILE__ );
57 Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) );
58 Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) );
59 Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) );
60 }
61
62 public static function module_configuration_load() {
63 // wp_safe_redirect( admin_url( 'options-general.php#configure-sso' ) );
64 // exit;
65 }
66
67 public static function module_configuration_head() {}
68
69 public static function module_configuration_screen() {
70 ?>
71 <form method="post" action="options.php">
72 <?php settings_fields( 'jetpack-sso' ); ?>
73 <?php do_settings_sections( 'jetpack-sso' ); ?>
74 <?php submit_button(); ?>
75 </form>
76 <?php
77 }
78
79 /**
80 * If jetpack_force_logout == 1 in current user meta the user will be forced
81 * to logout and reauthenticate with the site.
82 **/
83 public function maybe_logout_user() {
84 global $current_user;
85
86 if( 1 == $current_user->jetpack_force_logout ) {
87 delete_user_meta( $current_user->ID, 'jetpack_force_logout' );
88 self::delete_connection_for_user( $current_user->ID );
89 wp_logout();
90 wp_safe_redirect( wp_login_url() );
91 }
92 }
93
94
95 /**
96 * Adds additional methods the WordPress xmlrpc API for handling SSO specific features
97 *
98 * @param array $methods
99 * @return array
100 **/
101 public function xmlrpc_methods( $methods ) {
102 $methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' );
103 return $methods;
104 }
105
106 /**
107 * Marks a user's profile for disconnect from WordPress.com and forces a logout
108 * the next time the user visits the site.
109 **/
110 public function xmlrpc_user_disconnect( $user_id ) {
111 $user_query = new WP_User_Query(
112 array(
113 'meta_key' => 'wpcom_user_id',
114 'meta_value' => $user_id
115 )
116 );
117 $user = $user_query->get_results();
118 $user = $user[0];
119
120
121 if( $user instanceof WP_User ) {
122 $user = wp_set_current_user( $user->ID );
123 update_user_meta( $user->ID, 'jetpack_force_logout', '1' );
124 self::delete_connection_for_user( $user->ID );
125 return true;
126 }
127 return false;
128 }
129
130 /**
131 * Adds settings fields to Settings > General > Single Sign On that allows users to
132 * turn off the login form on wp-login.php
133 *
134 * @since 2.7
135 **/
136 public function register_settings() {
137
138 add_settings_section(
139 'jetpack_sso_settings',
140 __( 'Jetpack Single Sign On' , 'jetpack' ),
141 '__return_false',
142 'jetpack-sso'
143 );
144
145 /*
146 * Settings > General > Jetpack Single Sign On
147 * Checkbox for Remove default login form
148 */
149 /* Hide in 2.9
150 register_setting(
151 'general',
152 'jetpack_sso_remove_login_form',
153 array( $this, 'validate_settings_remove_login_form_checkbox' )
154 );
155
156 add_settings_field(
157 'jetpack_sso_remove_login_form',
158 __( 'Remove default login form?' , 'jetpack' ),
159 array( $this, 'render_remove_login_form_checkbox' ),
160 'general',
161 'jetpack_sso_settings'
162 );
163 */
164
165 /*
166 * Settings > General > Jetpack Single Sign On
167 * Require two step authentication
168 */
169 register_setting(
170 'jetpack-sso',
171 'jetpack_sso_require_two_step',
172 array( $this, 'validate_jetpack_sso_require_two_step' )
173 );
174
175 add_settings_field(
176 'jetpack_sso_require_two_step',
177 '', // __( 'Require Two-Step Authentication' , 'jetpack' ),
178 array( $this, 'render_require_two_step' ),
179 'jetpack-sso',
180 'jetpack_sso_settings'
181 );
182
183
184 /*
185 * Settings > General > Jetpack Single Sign On
186 */
187 register_setting(
188 'jetpack-sso',
189 'jetpack_sso_match_by_email',
190 array( $this, 'validate_jetpack_sso_match_by_email' )
191 );
192
193 add_settings_field(
194 'jetpack_sso_match_by_email',
195 '', // __( 'Match by Email' , 'jetpack' ),
196 array( $this, 'render_match_by_email' ),
197 'jetpack-sso',
198 'jetpack_sso_settings'
199 );
200 }
201
202 /**
203 * Builds the display for the checkbox allowing user to require two step
204 * auth be enabled on WordPress.com accounts before login. Displays in Settings > General
205 *
206 * @since 2.7
207 **/
208 public function render_require_two_step() {
209 echo '<label>';
210 echo '<input type="checkbox" name="jetpack_sso_require_two_step" ' . checked( 1 == get_option( 'jetpack_sso_require_two_step' ), true, false ) . '> ';
211 esc_html_e( 'Require Two-Step Authentication' , 'jetpack' );
212 echo '</label>';
213 }
214
215 /**
216 * Validate the require two step checkbox in Settings > General
217 *
218 * @since 2.7
219 * @return boolean
220 **/
221 public function validate_jetpack_sso_require_two_step( $input ) {
222 return ( ! empty( $input ) ) ? 1 : 0;
223 }
224
225 /**
226 * Builds the display for the checkbox allowing the user to allow matching logins by email
227 * Displays in Settings > General
228 *
229 * @since 2.9
230 **/
231 public function render_match_by_email() {
232 echo '<label>';
233 echo '<input type="checkbox" name="jetpack_sso_match_by_email"' . checked( 1 == get_option( 'jetpack_sso_match_by_email' ), true, false) . '> ';
234 esc_html_e( 'Match by Email', 'jetpack' );
235 echo '</label>';
236 }
237
238 /**
239 * Validate the match by email check in Settings > General
240 *
241 * @since 2.9
242 * @return boolean
243 **/
244 public function validate_jetpack_sso_match_by_email( $input ) {
245 return ( ! empty( $input ) ) ? 1 : 0;
246 }
247
248 /**
249 * Builds the display for the checkbox allowing users to remove the default
250 * WordPress login form from wp-login.php. Displays in Settings > General
251 *
252 * @since 2.7
253 **/
254 public function render_remove_login_form_checkbox() {
255 if( $this->is_user_connected( get_current_user_id() ) ) {
256 echo '<a name="configure-sso"></a>';
257 echo '<input type="checkbox" name="jetpack_sso_remove_login_form[remove_login_form]" ' . checked( 1 == get_option( 'jetpack_sso_remove_login_form' ), true, false ) . '>';
258 echo '<p class="description">Removes default login form and disallows login via POST</p>';
259 } else {
260 echo 'Your account must be connected to WordPress.com before disabling the login form.';
261 echo '<br/>' . $this->button();
262 }
263 }
264
265 /**
266 * Validate settings input from Settings > General
267 *
268 * @since 2.7
269 * @return boolean
270 **/
271 public function validate_settings_remove_login_form_checkbox( $input ) {
272 return ( isset( $input['remove_login_form'] ) )? 1: 0;
273 }
274
275 /**
276 * Removes 'Lost your password?' text from the login form if user
277 * does not want to show the login form
278 *
279 * @since 2.7
280 * @return string
281 **/
282 public function remove_lost_password_text( $text ) {
283 if( 'Lost your password?' == $text )
284 $text = '';
285 return $text;
286 }
287
288 /**
289 * Checks to determine if the user wants to login on wp-login
290 *
291 * This function mostly exists to cover the exceptions to login
292 * that may exist as other parameters to $_GET[action] as $_GET[action]
293 * does not have to exist. By default WordPress assumes login if an action
294 * is not set, however this may not be true, as in the case of logout
295 * where $_GET[loggedout] is instead set
296 *
297 * @return boolean
298 **/
299 private function wants_to_login() {
300 $wants_to_login = false;
301
302 // Cover default WordPress behavior
303 $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : 'login';
304
305 // And now the exceptions
306 $action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action;
307
308 if( 'login' == $action ) {
309 $wants_to_login = true;
310 }
311
312 return $wants_to_login;
313 }
314
315 private function bypass_login_forward_wpcom() {
316 return apply_filters( 'jetpack_sso_bypass_login_forward_wpcom', false );
317 }
318
319 function login_init() {
320 global $action;
321
322 /**
323 * If the user is attempting to logout AND the auto-forward to WordPress.com
324 * login is set then we need to ensure we do not auto-forward the user and get
325 * them stuck in an infinite logout loop.
326 */
327 if( isset( $_GET['loggedout'] ) && $this->bypass_login_forward_wpcom() ) {
328 add_filter( 'jetpack_remove_login_form', '__return_true' );
329 add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
330 }
331
332 /**
333 * Check to see if the site admin wants to automagically forward the user
334 * to the WordPress.com login page AND that the request to wp-login.php
335 * is not something other than login (Like logout!)
336 */
337 if (
338 $this->wants_to_login()
339 && $this->bypass_login_forward_wpcom()
340 ) {
341 add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
342 wp_safe_redirect( $this->build_sso_url() );
343 }
344
345 if ( 'login' === $action ) {
346 wp_enqueue_script( 'jquery' );
347 wp_enqueue_style( 'genericons' );
348 add_action( 'login_footer', array( $this, 'login_form' ) );
349 add_action( 'login_footer', array( $this, 'login_footer' ) );
350 /*
351 if ( get_option( 'jetpack_sso_remove_login_form' ) ) {
352 // Check to see if the user is attempting to login via the default login form.
353 // If so we need to deny it and forward elsewhere.
354 if( isset( $_REQUEST['wp-submit'] ) && 'Log In' == $_REQUEST['wp-submit'] ) {
355 wp_die( 'Login not permitted by this method. ');
356 }
357 add_filter( 'gettext', array( $this, 'remove_lost_password_text' ) );
358 }
359 */
360 } elseif ( 'jetpack-sso' === $action ) {
361 if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
362 $this->handle_login();
363 wp_enqueue_script( 'jquery' );
364 wp_enqueue_style( 'genericons' );
365 add_action( 'login_footer', array( $this, 'login_form' ) );
366 add_action( 'login_footer', array( $this, 'login_footer' ) );
367 } else {
368 if ( Jetpack::check_identity_crisis() ) {
369 wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
370 } else {
371 $this->maybe_save_cookie_redirect();
372 // Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
373 add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
374 wp_safe_redirect( $this->build_sso_url() );
375 }
376 }
377 }
378 }
379
380 /**
381 * Conditionally save the redirect_to url as a cookie.
382 */
383 public static function maybe_save_cookie_redirect() {
384 if ( headers_sent() ) {
385 return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) );
386 }
387
388 // If we have something to redirect to
389 if ( ! empty( $_GET['redirect_to'] ) ) {
390 $url = esc_url_raw( $_GET['redirect_to'] );
391 setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
392 // Otherwise, if it's already set
393 } elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
394 // Purge it.
395 setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
396 }
397
398 if ( ! empty( $_GET['rememberme'] ) ) {
399 setcookie( 'jetpack_sso_remember_me', '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, false, true );
400 } elseif ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
401 setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
402 }
403 }
404
405 /**
406 * Determine if the login form should be hidden or not
407 *
408 * Method is private only because it is only used in this class so far.
409 * Feel free to change it later
410 *
411 * @return bool
412 **/
413 private function should_hide_login_form() {
414 return apply_filters( 'jetpack_remove_login_form', get_option( 'jetpack_sso_remove_login_form', false ) );
415 }
416
417 function login_form() {
418 $classes = '';
419
420 if( $this->should_hide_login_form() ) {
421 $classes .= ' forced-sso';
422 }
423 echo '<div class="jetpack-sso-wrap' . $classes . '">' . $this->button() . '</div>';
424 }
425
426 function login_footer() {
427 $hide_login_form = $this->should_hide_login_form();
428 ?>
429 <style>
430 #loginform {
431 overflow: hidden;
432 padding-bottom: 26px;
433 }
434 .jetpack-sso-wrap {
435 <?php if ( $hide_login_form ) : ?>
436 text-align: center;
437 <?php else : ?>
438 float: right;
439 <?php endif; ?>
440 margin: 1em 0 0;
441 clear: right;
442 display: block;
443 }
444
445 <?php if ( $hide_login_form ) : ?>
446 .forced-sso .jetpack-sso.button {
447 font-size: 16px;
448 line-height: 27px;
449 height: 37px;
450 padding: 5px 12px 6px 47px;
451 }
452 .forced-sso .jetpack-sso.button:before {
453 font-size: 28px !important;
454 height: 37px;
455 padding: 5px 5px 4px;
456 width: 37px;
457 }
458 <?php endif; ?>
459 </style>
460 <script>
461 jQuery(document).ready(function($){
462 <?php if ( $hide_login_form ) : ?>
463 $( '#loginform' ).empty();
464 <?php endif; ?>
465 $( '#loginform' ).append( $( '.jetpack-sso-wrap' ) );
466
467 var $rememberme = $( '#rememberme' ),
468 $ssoButton = $( 'a.jetpack-sso.button' );
469
470 $rememberme.on( 'change', function() {
471 var url = $ssoButton.prop( 'href' ),
472 isChecked = $rememberme.prop( 'checked' ) ? 1 : 0;
473
474 if ( url.match( /&rememberme=\d/ ) ) {
475 url = url.replace( /&rememberme=\d/, '&rememberme=' + isChecked );
476 } else {
477 url += '&rememberme=' + isChecked;
478 }
479
480 $ssoButton.prop( 'href', url );
481 } ).change();
482
483 });
484 </script>
485 <?php
486 }
487
488 static function delete_connection_for_user( $user_id ) {
489 if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
490 return;
491 }
492 Jetpack::load_xml_rpc_client();
493 $xml = new Jetpack_IXR_Client( array(
494 'user_id' => $user_id
495 ) );
496 $xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
497
498 if ( $xml->isError() ) {
499 return false;
500 }
501
502 return $xml->getResponse();
503 }
504
505 static function request_initial_nonce() {
506 Jetpack::load_xml_rpc_client();
507 $xml = new Jetpack_IXR_Client( array(
508 'user_id' => get_current_user_id()
509 ) );
510 $xml->query( 'jetpack.sso.requestNonce' );
511
512 if ( $xml->isError() ) {
513 wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
514 }
515
516 return $xml->getResponse();
517 }
518
519 /**
520 * The function that actually handles the login!
521 */
522 function handle_login() {
523 $wpcom_nonce = sanitize_key( $_GET['sso_nonce'] );
524 $wpcom_user_id = (int) $_GET['user_id'];
525 $result = sanitize_key( $_GET['result'] );
526
527 Jetpack::load_xml_rpc_client();
528 $xml = new Jetpack_IXR_Client( array(
529 'user_id' => get_current_user_id()
530 ) );
531 $xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
532
533 if ( $xml->isError() ) {
534 wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
535 }
536
537 $user_data = $xml->getResponse();
538
539 if ( empty( $user_data ) ) {
540 wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
541 }
542
543 $user_data = (object) $user_data;
544 $user = null;
545 do_action( 'jetpack_sso_pre_handle_login', $user_data );
546
547 // Check to see if having two step enable on wpcom is a requirement to login here
548 $require_two_step = apply_filters( 'jetpack_sso_require_two_step', get_option( 'jetpack_sso_require_two_step' ) );
549 if( $require_two_step && 0 == (int) $user_data->two_step_enabled ) {
550 $this->user_data = $user_data;
551 do_action( 'wp_login_failed', $user_data->login );
552 add_action( 'login_message', array( $this, 'error_msg_enable_two_step' ) );
553 return;
554 }
555
556 if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
557 list( $state, $nonce ) = explode( '|', $_GET['state'] );
558
559 if ( wp_verify_nonce( $nonce, $state ) ) {
560 if ( 'sso-link-user' == $state ) {
561 $user = wp_get_current_user();
562 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
563 add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
564 }
565 } else wp_nonce_ays();
566 }
567
568 if ( empty( $user ) ) {
569 $user = $this->get_user_by_wpcom_id( $user_data->ID );
570 }
571
572 // If we don't have one by wpcom_user_id, try by the email?
573 if ( empty( $user ) && self::match_by_email() ) {
574 $user = get_user_by( 'email', $user_data->email );
575 if ( $user ) {
576 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
577 }
578 }
579
580 // If we've still got nothing, create the user.
581 if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
582 // If not matching by email we still need to verify the email does not exist
583 // or this blows up
584 /**
585 * If match_by_email is true, we know the email doesn't exist, as it would have
586 * been found in the first pass. If get_user_by( 'email' ) doesn't find the
587 * user, then we know that email is unused, so it's safe to add.
588 */
589 if ( self::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) {
590 $username = $user_data->login;
591
592 if ( username_exists( $username ) ) {
593 $username = $user_data->login . '_' . $user_data->ID;
594 }
595
596 $tries = 0;
597 while ( username_exists( $username ) ) {
598 $username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
599 if ( $tries++ >= 5 ) {
600 wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
601 }
602 }
603
604 $password = wp_generate_password( 20 );
605 $user_id = wp_create_user( $username, $password, $user_data->email );
606 $user = get_userdata( $user_id );
607
608 $user->display_name = $user_data->display_name;
609 $user->first_name = $user_data->first_name;
610 $user->last_name = $user_data->last_name;
611 $user->url = $user_data->url;
612 $user->description = $user_data->description;
613 wp_update_user( $user );
614
615 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
616 } else {
617 $this->user_data = $user_data;
618 // do_action( 'wp_login_failed', $user_data->login );
619 add_action( 'login_message', array( $this, 'error_msg_email_already_exists' ) );
620 return;
621 }
622 }
623
624 do_action( 'jetpack_sso_handle_login', $user, $user_data );
625
626 if ( $user ) {
627 // Cache the user's details, so we can present it back to them on their user screen.
628 update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
629
630 $remember = false;
631 if ( ! empty( $_COOKIE['jetpack_sso_remember_me'] ) ) {
632 $remember = true;
633 // And then purge it
634 setcookie( 'jetpack_sso_remember_me', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
635 }
636 // Set remember me value
637 $remember = apply_filters( 'jetpack_remember_login', $remember );
638 wp_set_auth_cookie( $user->ID, $remember );
639
640 // Run the WP core login action
641 do_action( 'wp_login', $user->user_login, $user );
642
643 $_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
644 $redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
645
646 // If we have a saved redirect to request in a cookie
647 if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) {
648 // Set that as the requested redirect to
649 $redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] );
650 // And then purge it
651 setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
652 }
653
654 wp_safe_redirect( apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user ) );
655 exit;
656 }
657
658 $this->user_data = $user_data;
659 do_action( 'wp_login_failed', $user_data->login );
660 add_action( 'login_message', array( $this, 'cant_find_user' ) );
661 }
662
663 static function profile_page_url() {
664 return admin_url( 'profile.php' );
665 }
666
667 static function match_by_email() {
668 $match_by_email = ( 1 == get_option( 'jetpack_sso_match_by_email', true ) ) ? true: false;
669 $match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : $match_by_email;
670
671 return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
672 }
673
674 static function new_user_override() {
675 $new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
676 return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
677 }
678
679 function allowed_redirect_hosts( $hosts ) {
680 if ( empty( $hosts ) ) {
681 $hosts = array();
682 }
683 $hosts[] = 'wordpress.com';
684
685 return array_unique( $hosts );
686 }
687
688 function button( $args = array() ) {
689 $defaults = array(
690 'action' => 'jetpack-sso',
691 );
692
693 $args = wp_parse_args( $args, $defaults );
694
695 if ( ! empty( $_GET['redirect_to'] ) ) {
696 $args['redirect_to'] = esc_url_raw( $_GET['redirect_to'] );
697 }
698
699 $url = add_query_arg( $args, wp_login_url() );
700
701 $css = "<style>
702 .jetpack-sso.button {
703 position: relative;
704 padding-left: 37px;
705 }
706 .jetpack-sso.button:before {
707 display: block;
708 box-sizing: border-box;
709 padding: 7px 0 0;
710 text-align: center;
711 position: absolute;
712 top: -1px;
713 left: -1px;
714 border-radius: 2px 0 0 2px;
715 content: '\\f205';
716 background: #0074a2;
717 color: #fff;
718 -webkit-font-smoothing: antialiased;
719 width: 30px;
720 height: 107%;
721 height: calc( 100% + 2px );
722 font: normal 22px/1 Genericons !important;
723 text-shadow: none;
724 }
725 @media screen and (min-width: 783px) {
726 .jetpack-sso.button:before {
727 padding-top: 3px;
728 }
729 }
730 .jetpack-sso.button:hover {
731 border: 1px solid #aaa;
732 }";
733
734 if ( version_compare( $GLOBALS['wp_version'], '3.8-alpha', '<' ) ) {
735 $css .= "
736 .jetpack-sso.button:before {
737 width: 25px;
738 font-size: 18px !important;
739 }
740 ";
741 }
742
743 $css .= "</style>";
744
745 $button = sprintf( '<a href="%1$s" class="jetpack-sso button">%2$s</a>', esc_url( $url ), esc_html__( 'Log in with WordPress.com', 'jetpack' ) );
746 return $button . $css;
747 }
748
749 function build_sso_url( $args = array() ) {
750 $defaults = array(
751 'action' => 'jetpack-sso',
752 'site_id' => Jetpack_Options::get_option( 'id' ),
753 'sso_nonce' => self::request_initial_nonce(),
754 );
755
756 if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
757 $defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
758 }
759
760 $args = wp_parse_args( $args, $defaults );
761 $url = add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
762
763 return $url;
764 }
765
766 /**
767 * Determines local user associated with a given WordPress.com user ID.
768 *
769 * @since 2.6.0
770 *
771 * @param int $wpcom_user_id User ID from WordPress.com
772 * @return object Local user object if found, null if not.
773 */
774 static function get_user_by_wpcom_id( $wpcom_user_id ) {
775 $user_query = new WP_User_Query( array(
776 'meta_key' => 'wpcom_user_id',
777 'meta_value' => intval( $wpcom_user_id ),
778 'number' => 1,
779 ) );
780
781 $users = $user_query->get_results();
782 return $users ? array_shift( $users ) : null;
783 }
784
785 /**
786 * Error message displayed on the login form when two step is required and
787 * the user's account on WordPress.com does not have two step enabled.
788 *
789 * @since 2.7
790 * @param string $message
791 * @return string
792 **/
793 public function error_msg_enable_two_step( $message ) {
794 $err = __( sprintf( 'This site requires two step authentication be enabled for your user account on WordPress.com. Please visit the <a href="%1$s"> Security Settings</a> page to enable two step', 'https://wordpress.com/me/security/two-step' ) , 'jetpack' );
795
796 $message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
797
798 return $message;
799 }
800
801 /**
802 * Error message displayed when the user tries to SSO, but match by email
803 * is off and they already have an account with their email address on
804 * this site.
805 *
806 * @param string $message
807 * @return string
808 */
809 public function error_msg_email_already_exists( $message ) {
810 $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' );
811
812 $message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
813
814 return $message;
815 }
816
817 /**
818 * Message displayed when the site admin has disabled the default WordPress
819 * login form in Settings > General > Single Sign On
820 *
821 * @since 2.7
822 * @param string $message
823 * @return string
824 **/
825 public function msg_login_by_jetpack( $message ) {
826
827 $msg = __( sprintf( 'Jetpack authenticates through WordPress.com — to log in, enter your WordPress.com username and password, or <a href="%1$s">visit WordPress.com</a> to create a free account now.', 'http://wordpress.com/signup' ) , 'jetpack' );
828
829 $msg = apply_filters( 'jetpack_sso_disclaimer_message', $msg );
830
831 $message .= sprintf( '<p class="message">%s</p>', $msg );
832 return $message;
833 }
834
835 /**
836 * Error message displayed on the login form when the user attempts
837 * to post to the login form and it is disabled.
838 *
839 * @since 2.8
840 * @param string $message
841 * @param string
842 **/
843 public function error_msg_login_method_not_allowed( $message ) {
844 $err = __( 'Login method not allowed' , 'jetpack' );
845 $message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
846
847 return $message;
848 }
849 function cant_find_user( $message ) {
850 if ( self::match_by_email() ) {
851 $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' );
852 } else {
853 $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' );
854 }
855 $err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
856 $message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
857 return $message;
858 }
859
860 /**
861 * Deal with user connections...
862 */
863 function admin_init() {
864 add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
865 add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
866
867 if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
868 $user = wp_get_current_user();
869 // Remove the connection on the wpcom end.
870 self::delete_connection_for_user( $user->ID );
871 // Clear it locally.
872 delete_user_meta( $user->ID, 'wpcom_user_id' );
873 delete_user_meta( $user->ID, 'wpcom_user_data' );
874 // Forward back to the profile page.
875 wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
876 }
877 }
878
879 /**
880 * Determines if a local user is connected to WordPress.com
881 *
882 * @since 2.8
883 * @param integer $user_id - Local user id
884 * @return boolean
885 **/
886 public function is_user_connected( $user_id ) {
887 return $this->get_user_data( $user_id ) ;
888 }
889
890 /**
891 * Retrieves a user's WordPress.com data
892 *
893 * @since 2.8
894 * @param integer $user_id - Local user id
895 * @return mixed null or stdClass
896 **/
897 public function get_user_data( $user_id ) {
898 return get_user_meta( $user_id, 'wpcom_user_data', true );
899 }
900
901 function edit_profile_fields( $user ) {
902 wp_enqueue_style( 'genericons' );
903 ?>
904
905 <h3><?php _e( 'WordPress.com Single Sign On', 'jetpack' ); ?></h3>
906 <p><?php _e( 'Connecting with WordPress.com SSO enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
907
908 <?php if ( $this->is_user_connected( $user->ID ) ) : /* If the user is currently connected... */ ?>
909 <?php $user_data = $this->get_user_data( $user->ID ); ?>
910 <table class="form-table jetpack-sso-form-table">
911 <tbody>
912 <tr>
913 <td>
914 <div class="profile-card">
915 <?php echo get_avatar( $user_data->email ); ?>
916 <p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
917 <p><?php echo esc_html( $user_data->login ); ?></p>
918 <span class="two_step">
919 <?php
920 if( $user_data->two_step_enabled ) {
921 ?> <p class="enabled"><a href="https://wordpress.com/me/security/two-step"><?php _e( 'Two-Step Authentication Enabled', 'jetpack' ); ?></a></p> <?php
922 } else {
923 ?> <p class="disabled"><a href="https://wordpress.com/me/security/two-step"><?php _e( 'Two-Step Authentication Disabled', 'jetpack' ); ?></a></p> <?php
924 }
925 ?>
926 </span>
927
928 </div>
929 <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>
930 </td>
931 </tr>
932 </tbody>
933 </table>
934
935 <style>
936 .jetpack-sso-form-table td {
937 padding-left: 0;
938 }
939
940 .jetpack-sso-form-table .profile-card {
941 padding: 10px;
942 background: #fff;
943 overflow: hidden;
944 max-width: 400px;
945 box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.1 );
946 margin-bottom: 1em;
947 }
948
949 .jetpack-sso-form-table .profile-card img {
950 float: left;
951 margin-right: 1em;
952 width: 48px;
953 height: 48px;
954 }
955
956 .jetpack-sso-form-table .profile-card .connected {
957 float: right;
958 margin-right: 0.5em;
959 color: #0a0;
960 }
961
962 .jetpack-sso-form-table .profile-card p {
963 margin-top: 0.7em;
964 font-size: 1.2em;
965 }
966
967 .jetpack-sso-form-table .profile-card .two_step .enabled a {
968 float: right;
969 color: #0a0;
970 }
971
972 .jetpack-sso-form-table .profile-card .two_step .disabled a {
973 float: right;
974 color: red;
975 }
976 </style>
977
978 <?php elseif ( get_current_user_id() == $user->ID ) : ?>
979
980 <?php echo $this->button( 'state=sso-link-user&_wpnonce=' . wp_create_nonce('sso-link-user') ); // update ?>
981
982 <?php else : ?>
983
984 <p><?php _e( 'This profile is not currently linked to a WordPress.com Profile.', 'jetpack' ); ?></p>
985
986 <?php endif;
987 }
988 }
989
990 Jetpack_SSO::get_instance();
991