PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 2.7.5
Jetpack – WP Security, Backup, Speed, & Growth v2.7.5
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 2.7.5, at modules/sso.php

404 lines 11.9 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: Let users login with their WordPress.com Credentials
6 * Sort Order: 50
7 * First Introduced: 2.6
8 * Requires Connection: Yes
9 * Auto Activate: No
10 * Module Tags: Developers
11 */
12
13
14 class Jetpack_SSO {
15 static $instance = null;
16
17 function __construct() {
18 if ( self::$instance ) {
19 return self::$instance;
20 }
21
22 self::$instance = $this;
23
24 add_action( 'admin_init', array( $this, 'admin_init' ) );
25 add_action( 'login_init', array( $this, 'login_init' ) );
26 add_action( 'delete_user', array( $this, 'delete_connection_for_user' ) );
27 }
28
29 function login_init() {
30 add_action( 'login_form', array( $this, 'login_form' ) );
31 add_action( 'login_footer', array( $this, 'login_footer' ) );
32 wp_enqueue_script( 'jquery' );
33 wp_enqueue_style( 'genericons' );
34
35 if ( isset( $_GET['action'] ) && 'jetpack-sso' == $_GET['action'] ) {
36 if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) {
37 $this->handle_login();
38 } else {
39 if ( Jetpack::check_identity_crisis() ) {
40 wp_die( __( "Error: This site's Jetpack connection is currently experiencing problems.", 'jetpack' ) );
41 } else {
42 // Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect?
43 add_filter( 'allowed_redirect_hosts', array( $this, 'allowed_redirect_hosts' ) );
44 wp_safe_redirect( $this->build_sso_url() );
45 }
46 }
47 }
48 }
49
50 function login_form() {
51 echo '<div class="jetpack-sso-wrap">' . $this->button() . '</div>';
52 }
53
54 function login_footer() {
55 ?>
56 <style>
57 #loginform {
58 overflow: hidden;
59 padding-bottom: 26px;
60 }
61 .jetpack-sso-wrap {
62 display: block;
63 float: right;
64 clear: right;
65 margin:1em 0 0;
66 }
67 </style>
68 <script>
69 jQuery(document).ready(function($){
70 $( '#loginform' ).append( $( '.jetpack-sso-wrap' ) );
71 });
72 </script>
73 <?php
74 }
75
76 static function delete_connection_for_user( $user_id ) {
77 if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) {
78 return;
79 }
80 Jetpack::load_xml_rpc_client();
81 $xml = new Jetpack_IXR_Client( array(
82 'user_id' => get_current_user_id()
83 ) );
84 $xml->query( 'jetpack.sso.removeUser', $wpcom_user_id );
85
86 if ( $xml->isError() ) {
87 return false;
88 }
89
90 return $xml->getResponse();
91 }
92
93 static function request_initial_nonce() {
94 Jetpack::load_xml_rpc_client();
95 $xml = new Jetpack_IXR_Client( array(
96 'user_id' => get_current_user_id()
97 ) );
98 $xml->query( 'jetpack.sso.requestNonce' );
99
100 if ( $xml->isError() ) {
101 wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
102 }
103
104 return $xml->getResponse();
105 }
106
107 function handle_login() {
108 $wpcom_nonce = sanitize_key( $_GET['sso_nonce'] );
109 $wpcom_user_id = (int) $_GET['user_id'];
110 $result = sanitize_key( $_GET['result'] );
111
112 Jetpack::load_xml_rpc_client();
113 $xml = new Jetpack_IXR_Client( array(
114 'user_id' => get_current_user_id()
115 ) );
116 $xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id );
117
118 if ( $xml->isError() ) {
119 wp_die( sprintf( '%s: %s', $xml->getErrorCode(), $xml->getErrorMessage() ) );
120 }
121
122 $user_data = $xml->getResponse();
123
124 if ( empty( $user_data ) ) {
125 wp_die( __( 'Error, invalid response data.', 'jetpack' ) );
126 }
127
128 $user_data = (object) $user_data;
129 $user = null;
130 do_action( 'jetpack_sso_pre_handle_login', $user_data );
131
132 if ( isset( $_GET['state'] ) && ( 0 < strpos( $_GET['state'], '|' ) ) ) {
133 list( $state, $nonce ) = explode( '|', $_GET['state'] );
134
135 if ( wp_verify_nonce( $nonce, $state ) ) {
136 if ( 'sso-link-user' == $state ) {
137 $user = wp_get_current_user();
138 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
139 add_filter( 'login_redirect', array( __CLASS__, 'profile_page_url' ) );
140 }
141 } else wp_nonce_ays();
142 }
143
144 if ( empty( $user ) ) {
145 $user = $this->get_user_by_wpcom_id( $user_data->ID );
146 }
147
148 // If we don't have one by wpcom_user_id, try by the email?
149 if ( empty( $user ) && self::match_by_email() ) {
150 $user = get_user_by( 'email', $user_data->email );
151 if ( $user ) {
152 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
153 }
154 }
155
156 // If we've still got nothing, create the user.
157 if ( empty( $user ) && ( get_option( 'users_can_register' ) || self::new_user_override() ) ) {
158 $username = $user_data->login;
159
160 if ( username_exists( $username ) ) {
161 $username = $user_data->login . '_' . $user_data->ID;
162 }
163
164 $tries = 0;
165 while ( username_exists( $username ) ) {
166 $username = $user_data->login . '_' . $user_data->ID . '_' . mt_rand();
167 if ( $tries++ >= 5 ) {
168 wp_die( __( "Error: Couldn't create suitable username.", 'jetpack' ) );
169 }
170 }
171
172 $password = wp_generate_password( 20 );
173 $user_id = wp_create_user( $username, $password, $user_data->email );
174 $user = get_userdata( $user_id );
175
176 $user->display_name = $user_data->display_name;
177 $user->first_name = $user_data->first_name;
178 $user->last_name = $user_data->last_name;
179 $user->url = $user_data->url;
180 $user->description = $user_data->description;
181 wp_update_user( $user );
182
183 update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID );
184 }
185
186 do_action( 'jetpack_sso_handle_login', $user, $user_data );
187
188 if ( $user ) {
189 // Cache the user's details, so we can present it back to them on their user screen.
190 update_user_meta( $user->ID, 'wpcom_user_data', $user_data );
191 wp_set_auth_cookie( $user->ID );
192
193 $_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : '';
194 $redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url();
195 wp_safe_redirect( apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user ) );
196 exit;
197 }
198
199 $this->user_data = $user_data;
200 add_action( 'login_message', array( $this, 'cant_find_user' ) );
201 }
202
203 static function profile_page_url() {
204 return admin_url( 'profile.php' );
205 }
206
207 static function match_by_email() {
208 $match_by_email = defined( 'WPCC_MATCH_BY_EMAIL' ) ? WPCC_MATCH_BY_EMAIL : true;
209 return apply_filters( 'jetpack_sso_match_by_email', $match_by_email );
210 }
211
212 static function new_user_override() {
213 $new_user_override = defined( 'WPCC_NEW_USER_OVERRIDE' ) ? WPCC_NEW_USER_OVERRIDE : false;
214 return apply_filters( 'jetpack_sso_new_user_override', $new_user_override );
215 }
216
217 function allowed_redirect_hosts( $hosts ) {
218 if ( empty( $hosts ) ) {
219 $hosts = array();
220 }
221 $hosts[] = 'wordpress.com';
222
223 return array_unique( $hosts );
224 }
225
226 function button( $args = array() ) {
227 $defaults = array(
228 'action' => 'jetpack-sso',
229 );
230
231 $args = wp_parse_args( $args, $defaults );
232 $url = add_query_arg( $args, wp_login_url() );
233
234 $css = "<style>
235 .jetpack-sso.button {
236 position: relative;
237 padding-left: 37px;
238 }
239 .jetpack-sso.button:before {
240 display: block;
241 padding: 3px 4px;
242 position: absolute;
243 top: -1px;
244 left: -1px;
245 border-radius: 2px 0 0 2px;
246 content: '\\f205';
247 background: #0074a2;
248 color: #fff;
249 -webkit-font-smoothing: antialiased;
250 width: 22px;
251 height: 22px;
252 font: normal 22px/1 Genericons !important;
253 text-shadow: none;
254 }
255 .jetpack-sso.button:active:before {
256 padding-top: 4px;
257 }
258 .jetpack-sso.button:hover {
259 border: 1px solid #aaa;
260 }";
261
262 if ( version_compare( $GLOBALS['wp_version'], '3.8-alpha', '<' ) ) {
263 $css .= "
264 .jetpack-sso.button:before {
265 width: 18px;
266 height: 18px;
267 font-size: 18px !important;
268 }
269 ";
270 }
271
272 $css .= "</style>";
273
274 return sprintf( '<a href="%1$s" class="jetpack-sso button">%2$s</a>', esc_url( $url ), esc_html__( 'Log in with WordPress.com', 'jetpack' ) ) . $css;
275 }
276
277 function build_sso_url( $args = array() ) {
278 $defaults = array(
279 'action' => 'jetpack-sso',
280 'site_id' => Jetpack_Options::get_option( 'id' ),
281 'sso_nonce' => self::request_initial_nonce(),
282 );
283
284 if ( isset( $_GET['state'] ) && check_admin_referer( $_GET['state'] ) ) {
285 $defaults['state'] = rawurlencode( $_GET['state'] . '|' . $_GET['_wpnonce'] );
286 }
287
288 $args = wp_parse_args( $args, $defaults );
289 $url = add_query_arg( $args, 'https://wordpress.com/wp-login.php' );
290
291 return $url;
292 }
293
294 function get_user_by_wpcom_id( $wpcom_user_id ) {
295 $user_query = new WP_User_Query( array(
296 'meta_key' => 'wpcom_user_id',
297 'meta_value' => intval( $wpcom_user_id ),
298 'number' => 1,
299 ) );
300
301 $users = $user_query->get_results();
302 return $users ? array_shift( $users ) : null;
303 }
304
305 function cant_find_user( $message ) {
306 if ( self::match_by_email() ) {
307 $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' );
308 } else {
309 $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' );
310 }
311 $err = sprintf( $err_format, $this->user_data->email, get_bloginfo( 'name' ) );
312 $message .= sprintf( '<p class="message" id="login_error">%s</p>', $err );
313 return $message;
314 }
315
316 /**
317 * Deal with user connections...
318 */
319 function admin_init() {
320 add_action( 'show_user_profile', array( $this, 'edit_profile_fields' ) ); // For their own profile
321 add_action( 'edit_user_profile', array( $this, 'edit_profile_fields' ) ); // For folks editing others profiles
322
323 if ( isset( $_GET['jetpack_sso'] ) && 'purge' == $_GET['jetpack_sso'] && check_admin_referer( 'jetpack_sso_purge' ) ) {
324 $user = wp_get_current_user();
325 // Remove the connection on the wpcom end.
326 self::delete_connection_for_user( $user->ID );
327 // Clear it locally.
328 delete_user_meta( $user->ID, 'wpcom_user_id' );
329 delete_user_meta( $user->ID, 'wpcom_user_data' );
330 // Forward back to the profile page.
331 wp_safe_redirect( remove_query_arg( array( 'jetpack_sso', '_wpnonce' ) ) );
332 }
333 }
334
335 function edit_profile_fields( $user ) {
336 ?>
337
338 <h3><?php _e( 'WordPress.com Single Sign On', 'jetpack' ); ?></h3>
339 <p><?php _e( 'Connecting with WordPress.com SSO enables you to log in via your WordPress.com account.', 'jetpack' ); ?></p>
340
341 <?php if ( ( $user_data = get_user_meta( $user->ID, 'wpcom_user_data', true ) ) && ! empty( $user_data->ID ) ) : /* If the user is currently connected... */ ?>
342
343 <table class="form-table jetpack-sso-form-table">
344 <tbody>
345 <tr>
346 <td>
347 <div class="profile-card">
348 <?php echo get_avatar( $user_data->email ); ?>
349 <p class="connected"><strong><?php _e( 'Connected', 'jetpack' ); ?></strong></p>
350 <p><?php echo esc_html( $user_data->login ); ?></p>
351 </div>
352 <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>
353 </td>
354 </tr>
355 </tbody>
356 </table>
357
358 <style>
359 .jetpack-sso-form-table td {
360 padding-left: 0;
361 }
362
363 .jetpack-sso-form-table .profile-card {
364 padding: 10px;
365 background: #fff;
366 overflow: hidden;
367 max-width: 400px;
368 box-shadow: 0 1px 2px rgba( 0, 0, 0, 0.1 );
369 margin-bottom: 1em;
370 }
371
372 .jetpack-sso-form-table .profile-card img {
373 float: left;
374 margin-right: 1em;
375 width: 48px;
376 height: 48px;
377 }
378
379 .jetpack-sso-form-table .profile-card .connected {
380 float: right;
381 margin-right: 0.5em;
382 color: #0a0;
383 }
384
385 .jetpack-sso-form-table .profile-card p {
386 margin-top: 0.7em;
387 font-size: 1.2em;
388 }
389 </style>
390
391 <?php elseif ( get_current_user_id() == $user->ID ) : ?>
392
393 <?php echo $this->button( 'state=sso-link-user&_wpnonce=' . wp_create_nonce('sso-link-user') ); // update ?>
394
395 <?php else : ?>
396
397 <p><?php _e( 'This profile is not currently linked to a WordPress.com Profile.', 'jetpack' ); ?></p>
398
399 <?php endif;
400 }
401 }
402
403 new Jetpack_SSO;
404