/modules/sso.php

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