Jetpack_SSO

Module Name: Single Sign On Module Description: Allow users to log into this site using WordPress.com accounts Jumpstart Description: Lets you log in to all your Jetpack-enabled sites with one click using your WordPress.com account.

Defined (1)

The class is defined in the following location(s).

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