1
|
|
|
<?php |
2
|
|
|
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-helpers.php' ); |
3
|
|
|
require_once( JETPACK__PLUGIN_DIR . 'modules/sso/class.jetpack-sso-notices.php' ); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Module Name: Secure Sign On |
7
|
|
|
* Module Description: Allow users to log into this site using WordPress.com accounts |
8
|
|
|
* Jumpstart Description: Lets you log in to all your Jetpack-enabled sites with one click using your WordPress.com account. |
9
|
|
|
* Sort Order: 30 |
10
|
|
|
* Recommendation Order: 5 |
11
|
|
|
* First Introduced: 2.6 |
12
|
|
|
* Requires Connection: Yes |
13
|
|
|
* Auto Activate: No |
14
|
|
|
* Module Tags: Developers |
15
|
|
|
* Feature: Security, Jumpstart |
16
|
|
|
* Additional Search Queries: sso, single sign on, login, log in |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
class Jetpack_SSO { |
20
|
|
|
static $instance = null; |
21
|
|
|
|
22
|
|
|
private function __construct() { |
23
|
|
|
|
24
|
|
|
self::$instance = $this; |
25
|
|
|
|
26
|
|
|
add_action( 'admin_init', array( $this, 'maybe_authorize_user_after_sso' ), 1 ); |
27
|
|
|
add_action( 'admin_init', array( $this, 'register_settings' ) ); |
28
|
|
|
add_action( 'login_init', array( $this, 'login_init' ) ); |
29
|
|
|
add_action( 'delete_user', array( $this, 'delete_connection_for_user' ) ); |
30
|
|
|
add_filter( 'jetpack_xmlrpc_methods', array( $this, 'xmlrpc_methods' ) ); |
31
|
|
|
add_action( 'init', array( $this, 'maybe_logout_user' ), 5 ); |
32
|
|
|
add_action( 'jetpack_modules_loaded', array( $this, 'module_configure_button' ) ); |
33
|
|
|
add_action( 'login_form_logout', array( $this, 'store_wpcom_profile_cookies_on_logout' ) ); |
34
|
|
|
add_action( 'jetpack_unlinked_user', array( $this, 'delete_connection_for_user') ); |
35
|
|
|
add_action( 'wp_login', array( 'Jetpack_SSO', 'clear_cookies_after_login' ) ); |
36
|
|
|
add_action( 'jetpack_jitm_received_envelopes', array( $this, 'inject_sso_jitm' ) ); |
37
|
|
|
|
38
|
|
|
// Adding this action so that on login_init, the action won't be sanitized out of the $action global. |
39
|
|
|
add_action( 'login_form_jetpack-sso', '__return_true' ); |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Returns the single instance of the Jetpack_SSO object |
44
|
|
|
* |
45
|
|
|
* @since 2.8 |
46
|
|
|
* @return Jetpack_SSO |
47
|
|
|
**/ |
48
|
|
|
public static function get_instance() { |
49
|
|
|
if ( ! is_null( self::$instance ) ) { |
50
|
|
|
return self::$instance; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
return self::$instance = new Jetpack_SSO; |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Add configure button and functionality to the module card on the Jetpack screen |
58
|
|
|
**/ |
59
|
|
|
public static function module_configure_button() { |
60
|
|
|
Jetpack::enable_module_configurable( __FILE__ ); |
61
|
|
|
Jetpack::module_configuration_load( __FILE__, array( __CLASS__, 'module_configuration_load' ) ); |
62
|
|
|
Jetpack::module_configuration_head( __FILE__, array( __CLASS__, 'module_configuration_head' ) ); |
63
|
|
|
Jetpack::module_configuration_screen( __FILE__, array( __CLASS__, 'module_configuration_screen' ) ); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
public static function module_configuration_load() {} |
67
|
|
|
|
68
|
|
|
public static function module_configuration_head() {} |
69
|
|
|
|
70
|
|
|
public static function module_configuration_screen() { |
71
|
|
|
?> |
72
|
|
|
<form method="post" action="options.php"> |
73
|
|
|
<?php settings_fields( 'jetpack-sso' ); ?> |
74
|
|
|
<?php do_settings_sections( 'jetpack-sso' ); ?> |
75
|
|
|
<?php submit_button(); ?> |
76
|
|
|
</form> |
77
|
|
|
<?php |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* If jetpack_force_logout == 1 in current user meta the user will be forced |
82
|
|
|
* to logout and reauthenticate with the site. |
83
|
|
|
**/ |
84
|
|
|
public function maybe_logout_user() { |
85
|
|
|
global $current_user; |
86
|
|
|
|
87
|
|
|
if ( 1 == $current_user->jetpack_force_logout ) { |
88
|
|
|
delete_user_meta( $current_user->ID, 'jetpack_force_logout' ); |
89
|
|
|
self::delete_connection_for_user( $current_user->ID ); |
90
|
|
|
wp_logout(); |
91
|
|
|
wp_safe_redirect( wp_login_url() ); |
92
|
|
|
exit; |
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Adds additional methods the WordPress xmlrpc API for handling SSO specific features |
98
|
|
|
* |
99
|
|
|
* @param array $methods |
100
|
|
|
* @return array |
101
|
|
|
**/ |
102
|
|
|
public function xmlrpc_methods( $methods ) { |
103
|
|
|
$methods['jetpack.userDisconnect'] = array( $this, 'xmlrpc_user_disconnect' ); |
104
|
|
|
return $methods; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* Marks a user's profile for disconnect from WordPress.com and forces a logout |
109
|
|
|
* the next time the user visits the site. |
110
|
|
|
**/ |
111
|
|
|
public function xmlrpc_user_disconnect( $user_id ) { |
112
|
|
|
$user_query = new WP_User_Query( |
113
|
|
|
array( |
114
|
|
|
'meta_key' => 'wpcom_user_id', |
115
|
|
|
'meta_value' => $user_id, |
116
|
|
|
) |
117
|
|
|
); |
118
|
|
|
$user = $user_query->get_results(); |
119
|
|
|
$user = $user[0]; |
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
|
|
|
* Enqueues scripts and styles necessary for SSO login. |
132
|
|
|
*/ |
133
|
|
|
public function login_enqueue_scripts() { |
134
|
|
|
global $action; |
135
|
|
|
|
136
|
|
|
if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) { |
137
|
|
|
return; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
if ( is_rtl() ) { |
141
|
|
|
wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login-rtl.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION ); |
142
|
|
|
} else { |
143
|
|
|
wp_enqueue_style( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.css', JETPACK__PLUGIN_FILE ), array( 'login', 'genericons' ), JETPACK__VERSION ); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
wp_enqueue_script( 'jetpack-sso-login', plugins_url( 'modules/sso/jetpack-sso-login.js', JETPACK__PLUGIN_FILE ), array( 'jquery' ), JETPACK__VERSION ); |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
/** |
150
|
|
|
* Adds Jetpack SSO classes to login body |
151
|
|
|
* |
152
|
|
|
* @param array $classes Array of classes to add to body tag |
153
|
|
|
* @return array Array of classes to add to body tag |
154
|
|
|
*/ |
155
|
|
|
public function login_body_class( $classes ) { |
156
|
|
|
global $action; |
157
|
|
|
|
158
|
|
|
if ( ! Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) { |
159
|
|
|
return $classes; |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// Always add the jetpack-sso class so that we can add SSO specific styling even when the SSO form isn't being displayed. |
163
|
|
|
$classes[] = 'jetpack-sso'; |
164
|
|
|
|
165
|
|
|
if ( ! Jetpack::is_staging_site() ) { |
166
|
|
|
/** |
167
|
|
|
* Should we show the SSO login form? |
168
|
|
|
* |
169
|
|
|
* $_GET['jetpack-sso-default-form'] is used to provide a fallback in case JavaScript is not enabled. |
170
|
|
|
* |
171
|
|
|
* The default_to_sso_login() method allows us to dynamically decide whether we show the SSO login form or not. |
172
|
|
|
* The SSO module uses the method to display the default login form if we can not find a user to log in via SSO. |
173
|
|
|
* But, the method could be filtered by a site admin to always show the default login form if that is preferred. |
174
|
|
|
*/ |
175
|
|
|
if ( empty( $_GET['jetpack-sso-show-default-form'] ) && Jetpack_SSO_Helpers::show_sso_login() ) { |
176
|
|
|
$classes[] = 'jetpack-sso-form-display'; |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
return $classes; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
public function print_inline_admin_css() { |
184
|
|
|
?> |
185
|
|
|
<style> |
186
|
|
|
.jetpack-sso .message { |
187
|
|
|
margin-top: 20px; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
.jetpack-sso #login .message:first-child, |
191
|
|
|
.jetpack-sso #login h1 + .message { |
192
|
|
|
margin-top: 0; |
193
|
|
|
} |
194
|
|
|
</style> |
195
|
|
|
<?php |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Adds settings fields to Settings > General > Secure Sign On that allows users to |
200
|
|
|
* turn off the login form on wp-login.php |
201
|
|
|
* |
202
|
|
|
* @since 2.7 |
203
|
|
|
**/ |
204
|
|
|
public function register_settings() { |
205
|
|
|
|
206
|
|
|
add_settings_section( |
207
|
|
|
'jetpack_sso_settings', |
208
|
|
|
__( 'Secure Sign On' , 'jetpack' ), |
209
|
|
|
'__return_false', |
210
|
|
|
'jetpack-sso' |
211
|
|
|
); |
212
|
|
|
|
213
|
|
|
/* |
214
|
|
|
* Settings > General > Secure Sign On |
215
|
|
|
* Require two step authentication |
216
|
|
|
*/ |
217
|
|
|
register_setting( |
218
|
|
|
'jetpack-sso', |
219
|
|
|
'jetpack_sso_require_two_step', |
220
|
|
|
array( $this, 'validate_jetpack_sso_require_two_step' ) |
221
|
|
|
); |
222
|
|
|
|
223
|
|
|
add_settings_field( |
224
|
|
|
'jetpack_sso_require_two_step', |
225
|
|
|
'', // __( 'Require Two-Step Authentication' , 'jetpack' ), |
226
|
|
|
array( $this, 'render_require_two_step' ), |
227
|
|
|
'jetpack-sso', |
228
|
|
|
'jetpack_sso_settings' |
229
|
|
|
); |
230
|
|
|
|
231
|
|
|
/* |
232
|
|
|
* Settings > General > Secure Sign On |
233
|
|
|
*/ |
234
|
|
|
register_setting( |
235
|
|
|
'jetpack-sso', |
236
|
|
|
'jetpack_sso_match_by_email', |
237
|
|
|
array( $this, 'validate_jetpack_sso_match_by_email' ) |
238
|
|
|
); |
239
|
|
|
|
240
|
|
|
add_settings_field( |
241
|
|
|
'jetpack_sso_match_by_email', |
242
|
|
|
'', // __( 'Match by Email' , 'jetpack' ), |
243
|
|
|
array( $this, 'render_match_by_email' ), |
244
|
|
|
'jetpack-sso', |
245
|
|
|
'jetpack_sso_settings' |
246
|
|
|
); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Builds the display for the checkbox allowing user to require two step |
251
|
|
|
* auth be enabled on WordPress.com accounts before login. Displays in Settings > General |
252
|
|
|
* |
253
|
|
|
* @since 2.7 |
254
|
|
|
**/ |
255
|
|
|
public function render_require_two_step() { |
256
|
|
|
?> |
257
|
|
|
<label> |
258
|
|
|
<input |
259
|
|
|
type="checkbox" |
260
|
|
|
name="jetpack_sso_require_two_step" |
261
|
|
|
<?php checked( Jetpack_SSO_Helpers::is_two_step_required() ); ?> |
262
|
|
|
<?php disabled( Jetpack_SSO_Helpers::is_require_two_step_checkbox_disabled() ); ?> |
263
|
|
|
> |
264
|
|
|
<?php esc_html_e( 'Require Two-Step Authentication' , 'jetpack' ); ?> |
265
|
|
|
</label> |
266
|
|
|
<?php |
267
|
|
|
} |
268
|
|
|
|
269
|
|
|
/** |
270
|
|
|
* Validate the require two step checkbox in Settings > General |
271
|
|
|
* |
272
|
|
|
* @since 2.7 |
273
|
|
|
* @return boolean |
274
|
|
|
**/ |
275
|
|
|
public function validate_jetpack_sso_require_two_step( $input ) { |
276
|
|
|
return ( ! empty( $input ) ) ? 1 : 0; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Builds the display for the checkbox allowing the user to allow matching logins by email |
281
|
|
|
* Displays in Settings > General |
282
|
|
|
* |
283
|
|
|
* @since 2.9 |
284
|
|
|
**/ |
285
|
|
|
public function render_match_by_email() { |
286
|
|
|
?> |
287
|
|
|
<label> |
288
|
|
|
<input |
289
|
|
|
type="checkbox" |
290
|
|
|
name="jetpack_sso_match_by_email" |
291
|
|
|
<?php checked( Jetpack_SSO_Helpers::match_by_email() ); ?> |
292
|
|
|
<?php disabled( Jetpack_SSO_Helpers::is_match_by_email_checkbox_disabled() ); ?> |
293
|
|
|
> |
294
|
|
|
<?php esc_html_e( 'Match by Email', 'jetpack' ); ?> |
295
|
|
|
</label> |
296
|
|
|
<?php |
297
|
|
|
} |
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Validate the match by email check in Settings > General |
301
|
|
|
* |
302
|
|
|
* @since 2.9 |
303
|
|
|
* @return boolean |
304
|
|
|
**/ |
305
|
|
|
public function validate_jetpack_sso_match_by_email( $input ) { |
306
|
|
|
return ( ! empty( $input ) ) ? 1 : 0; |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* Checks to determine if the user wants to login on wp-login |
311
|
|
|
* |
312
|
|
|
* This function mostly exists to cover the exceptions to login |
313
|
|
|
* that may exist as other parameters to $_GET[action] as $_GET[action] |
314
|
|
|
* does not have to exist. By default WordPress assumes login if an action |
315
|
|
|
* is not set, however this may not be true, as in the case of logout |
316
|
|
|
* where $_GET[loggedout] is instead set |
317
|
|
|
* |
318
|
|
|
* @return boolean |
319
|
|
|
**/ |
320
|
|
|
private function wants_to_login() { |
321
|
|
|
$wants_to_login = false; |
322
|
|
|
|
323
|
|
|
// Cover default WordPress behavior |
324
|
|
|
$action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : 'login'; |
325
|
|
|
|
326
|
|
|
// And now the exceptions |
327
|
|
|
$action = isset( $_GET['loggedout'] ) ? 'loggedout' : $action; |
328
|
|
|
|
329
|
|
|
if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) { |
330
|
|
|
$wants_to_login = true; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
return $wants_to_login; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
function login_init() { |
337
|
|
|
global $action; |
338
|
|
|
|
339
|
|
|
if ( Jetpack_SSO_Helpers::should_hide_login_form() ) { |
340
|
|
|
/** |
341
|
|
|
* Since the default authenticate filters fire at priority 20 for checking username and password, |
342
|
|
|
* let's fire at priority 30. wp_authenticate_spam_check is fired at priority 99, but since we return a |
343
|
|
|
* WP_Error in disable_default_login_form, then we won't trigger spam processing logic. |
344
|
|
|
*/ |
345
|
|
|
add_filter( 'authenticate', array( 'Jetpack_SSO_Notices', 'disable_default_login_form' ), 30 ); |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Filter the display of the disclaimer message appearing when default WordPress login form is disabled. |
349
|
|
|
* |
350
|
|
|
* @module sso |
351
|
|
|
* |
352
|
|
|
* @since 2.8.0 |
353
|
|
|
* |
354
|
|
|
* @param bool true Should the disclaimer be displayed. Default to true. |
355
|
|
|
*/ |
356
|
|
|
$display_sso_disclaimer = apply_filters( 'jetpack_sso_display_disclaimer', true ); |
357
|
|
|
if ( $display_sso_disclaimer ) { |
358
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'msg_login_by_jetpack' ) ); |
359
|
|
|
} |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
if ( 'jetpack-sso' === $action ) { |
363
|
|
|
if ( isset( $_GET['result'], $_GET['user_id'], $_GET['sso_nonce'] ) && 'success' == $_GET['result'] ) { |
364
|
|
|
$this->handle_login(); |
365
|
|
|
$this->display_sso_login_form(); |
366
|
|
|
} else { |
367
|
|
|
if ( Jetpack::is_staging_site() ) { |
368
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) ); |
369
|
|
|
} else { |
370
|
|
|
// Is it wiser to just use wp_redirect than do this runaround to wp_safe_redirect? |
371
|
|
|
add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) ); |
372
|
|
|
$reauth = ! empty( $_GET['force_reauth'] ); |
373
|
|
|
$sso_url = $this->get_sso_url_or_die( $reauth ); |
374
|
|
|
|
375
|
|
|
// Is this our first SSO Login. Set an option. |
376
|
|
|
if ( ! Jetpack_Options::get_option( 'sso_first_login' ) ) { |
377
|
|
|
Jetpack_options::update_option( 'sso_first_login', true ); |
378
|
|
|
} |
379
|
|
|
|
380
|
|
|
JetpackTracking::record_user_event( 'sso_login_redirect_success' ); |
381
|
|
|
wp_safe_redirect( $sso_url ); |
382
|
|
|
exit; |
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
} else if ( Jetpack_SSO_Helpers::display_sso_form_for_action( $action ) ) { |
386
|
|
|
|
387
|
|
|
// Save cookies so we can handle redirects after SSO |
388
|
|
|
$this->save_cookies(); |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Check to see if the site admin wants to automagically forward the user |
392
|
|
|
* to the WordPress.com login page AND that the request to wp-login.php |
393
|
|
|
* is not something other than login (Like logout!) |
394
|
|
|
*/ |
395
|
|
|
if ( Jetpack_SSO_Helpers::bypass_login_forward_wpcom() && $this->wants_to_login() ) { |
396
|
|
|
add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) ); |
397
|
|
|
$reauth = ! empty( $_GET['force_reauth'] ); |
398
|
|
|
$sso_url = $this->get_sso_url_or_die( $reauth ); |
399
|
|
|
JetpackTracking::record_user_event( 'sso_login_redirect_bypass_success' ); |
400
|
|
|
wp_safe_redirect( $sso_url ); |
401
|
|
|
exit; |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
$this->display_sso_login_form(); |
405
|
|
|
} |
406
|
|
|
} |
407
|
|
|
|
408
|
|
|
/** |
409
|
|
|
* Ensures that we can get a nonce from WordPress.com via XML-RPC before setting |
410
|
|
|
* up the hooks required to display the SSO form. |
411
|
|
|
*/ |
412
|
|
|
public function display_sso_login_form() { |
413
|
|
|
add_filter( 'login_body_class', array( $this, 'login_body_class' ) ); |
414
|
|
|
add_action( 'login_head', array( $this, 'print_inline_admin_css' ) ); |
415
|
|
|
|
416
|
|
|
if ( Jetpack::is_staging_site() ) { |
417
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'sso_not_allowed_in_staging' ) ); |
418
|
|
|
return; |
419
|
|
|
} |
420
|
|
|
|
421
|
|
|
$sso_nonce = self::request_initial_nonce(); |
422
|
|
|
if ( is_wp_error( $sso_nonce ) ) { |
423
|
|
|
return; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
add_action( 'login_form', array( $this, 'login_form' ) ); |
427
|
|
|
add_action( 'login_enqueue_scripts', array( $this, 'login_enqueue_scripts' ) ); |
428
|
|
|
} |
429
|
|
|
|
430
|
|
|
/** |
431
|
|
|
* Conditionally save the redirect_to url as a cookie. |
432
|
|
|
* |
433
|
|
|
* @since 4.6.0 Renamed to save_cookies from maybe_save_redirect_cookies |
434
|
|
|
*/ |
435
|
|
|
public static function save_cookies() { |
436
|
|
|
if ( headers_sent() ) { |
437
|
|
|
return new WP_Error( 'headers_sent', __( 'Cannot deal with cookie redirects, as headers are already sent.', 'jetpack' ) ); |
438
|
|
|
} |
439
|
|
|
|
440
|
|
|
setcookie( |
441
|
|
|
'jetpack_sso_original_request', |
442
|
|
|
esc_url_raw( set_url_scheme( $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ), |
443
|
|
|
time() + HOUR_IN_SECONDS, |
444
|
|
|
COOKIEPATH, |
445
|
|
|
COOKIE_DOMAIN, |
446
|
|
|
is_ssl(), |
447
|
|
|
true |
448
|
|
|
); |
449
|
|
|
|
450
|
|
|
if ( ! empty( $_GET['redirect_to'] ) ) { |
451
|
|
|
// If we have something to redirect to |
452
|
|
|
$url = esc_url_raw( $_GET['redirect_to'] ); |
453
|
|
|
setcookie( 'jetpack_sso_redirect_to', $url, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); |
454
|
|
|
} elseif ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) { |
455
|
|
|
// Otherwise, if it's already set, purge it. |
456
|
|
|
setcookie( 'jetpack_sso_redirect_to', ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Outputs the Jetpack SSO button and description as well as the toggle link |
462
|
|
|
* for switching between Jetpack SSO and default login. |
463
|
|
|
*/ |
464
|
|
|
function login_form() { |
465
|
|
|
$site_name = get_bloginfo( 'name' ); |
466
|
|
|
if ( ! $site_name ) { |
467
|
|
|
$site_name = get_bloginfo( 'url' ); |
468
|
|
|
} |
469
|
|
|
|
470
|
|
|
$display_name = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) |
471
|
|
|
? $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] |
472
|
|
|
: false; |
473
|
|
|
$gravatar = ! empty( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) |
474
|
|
|
? $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] |
475
|
|
|
: false; |
476
|
|
|
|
477
|
|
|
?> |
478
|
|
|
<div id="jetpack-sso-wrap"> |
479
|
|
|
<?php if ( $display_name && $gravatar ) : ?> |
480
|
|
|
<div id="jetpack-sso-wrap__user"> |
481
|
|
|
<img width="72" height="72" src="<?php echo esc_html( $gravatar ); ?>" /> |
482
|
|
|
|
483
|
|
|
<h2> |
484
|
|
|
<?php |
485
|
|
|
echo wp_kses( |
486
|
|
|
sprintf( __( 'Log in as <span>%s</span>', 'jetpack' ), esc_html( $display_name ) ), |
487
|
|
|
array( 'span' => true ) |
488
|
|
|
); |
489
|
|
|
?> |
490
|
|
|
</h2> |
491
|
|
|
</div> |
492
|
|
|
|
493
|
|
|
<?php endif; ?> |
494
|
|
|
|
495
|
|
|
|
496
|
|
|
<div id="jetpack-sso-wrap__action"> |
497
|
|
|
<?php echo $this->build_sso_button( array(), 'is_primary' ); ?> |
|
|
|
|
498
|
|
|
|
499
|
|
|
<?php if ( $display_name && $gravatar ) : ?> |
500
|
|
|
<a rel="nofollow" class="jetpack-sso-wrap__reauth" href="<?php echo esc_url( $this->build_sso_button_url( array( 'force_reauth' => '1' ) ) ); ?>"> |
501
|
|
|
<?php esc_html_e( 'Log in as a different WordPress.com user', 'jetpack' ); ?> |
502
|
|
|
</a> |
503
|
|
|
<?php else : ?> |
504
|
|
|
<p> |
505
|
|
|
<?php |
506
|
|
|
echo esc_html( |
507
|
|
|
sprintf( |
508
|
|
|
__( 'You can now save time spent logging in by connecting your WordPress.com account to %s.', 'jetpack' ), |
509
|
|
|
esc_html( $site_name ) |
510
|
|
|
) |
511
|
|
|
); |
512
|
|
|
?> |
513
|
|
|
</p> |
514
|
|
|
<?php endif; ?> |
515
|
|
|
</div> |
516
|
|
|
|
517
|
|
|
<?php if ( ! Jetpack_SSO_Helpers::should_hide_login_form() ) : ?> |
518
|
|
|
<div class="jetpack-sso-or"> |
519
|
|
|
<span><?php esc_html_e( 'Or', 'jetpack' ); ?></span> |
520
|
|
|
</div> |
521
|
|
|
|
522
|
|
|
<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '1' ) ); ?>" class="jetpack-sso-toggle wpcom"> |
523
|
|
|
<?php |
524
|
|
|
esc_html_e( 'Log in with username and password', 'jetpack' ) |
525
|
|
|
?> |
526
|
|
|
</a> |
527
|
|
|
|
528
|
|
|
<a href="<?php echo esc_url( add_query_arg( 'jetpack-sso-show-default-form', '0' ) ); ?>" class="jetpack-sso-toggle default"> |
529
|
|
|
<?php |
530
|
|
|
esc_html_e( 'Log in with WordPress.com', 'jetpack' ) |
531
|
|
|
?> |
532
|
|
|
</a> |
533
|
|
|
<?php endif; ?> |
534
|
|
|
</div> |
535
|
|
|
<?php |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
/** |
539
|
|
|
* Clear the cookies that store the profile information for the last |
540
|
|
|
* WPCOM user to connect. |
541
|
|
|
*/ |
542
|
|
|
static function clear_wpcom_profile_cookies() { |
543
|
|
View Code Duplication |
if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_name_' . COOKIEHASH ] ) ) { |
544
|
|
|
setcookie( |
545
|
|
|
'jetpack_sso_wpcom_name_' . COOKIEHASH, |
546
|
|
|
' ', |
547
|
|
|
time() - YEAR_IN_SECONDS, |
548
|
|
|
COOKIEPATH, |
549
|
|
|
COOKIE_DOMAIN, |
550
|
|
|
is_ssl() |
551
|
|
|
); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
View Code Duplication |
if ( isset( $_COOKIE[ 'jetpack_sso_wpcom_gravatar_' . COOKIEHASH ] ) ) { |
555
|
|
|
setcookie( |
556
|
|
|
'jetpack_sso_wpcom_gravatar_' . COOKIEHASH, |
557
|
|
|
' ', |
558
|
|
|
time() - YEAR_IN_SECONDS, |
559
|
|
|
COOKIEPATH, |
560
|
|
|
COOKIE_DOMAIN, |
561
|
|
|
is_ssl() |
562
|
|
|
); |
563
|
|
|
} |
564
|
|
|
} |
565
|
|
|
|
566
|
|
|
/** |
567
|
|
|
* Clear cookies that are no longer needed once the user has logged in. |
568
|
|
|
* |
569
|
|
|
* @since 4.8.0 |
570
|
|
|
*/ |
571
|
|
|
static function clear_cookies_after_login() { |
572
|
|
|
self::clear_wpcom_profile_cookies(); |
573
|
|
View Code Duplication |
if ( isset( $_COOKIE[ 'jetpack_sso_nonce' ] ) ) { |
574
|
|
|
setcookie( |
575
|
|
|
'jetpack_sso_nonce', |
576
|
|
|
' ', |
577
|
|
|
time() - YEAR_IN_SECONDS, |
578
|
|
|
COOKIEPATH, |
579
|
|
|
COOKIE_DOMAIN, |
580
|
|
|
is_ssl() |
581
|
|
|
); |
582
|
|
|
} |
583
|
|
|
|
584
|
|
View Code Duplication |
if ( isset( $_COOKIE[ 'jetpack_sso_original_request' ] ) ) { |
585
|
|
|
setcookie( |
586
|
|
|
'jetpack_sso_original_request', |
587
|
|
|
' ', |
588
|
|
|
time() - YEAR_IN_SECONDS, |
589
|
|
|
COOKIEPATH, |
590
|
|
|
COOKIE_DOMAIN, |
591
|
|
|
is_ssl() |
592
|
|
|
); |
593
|
|
|
} |
594
|
|
|
|
595
|
|
View Code Duplication |
if ( isset( $_COOKIE[ 'jetpack_sso_redirect_to' ] ) ) { |
596
|
|
|
setcookie( |
597
|
|
|
'jetpack_sso_redirect_to', |
598
|
|
|
' ', |
599
|
|
|
time() - YEAR_IN_SECONDS, |
600
|
|
|
COOKIEPATH, |
601
|
|
|
COOKIE_DOMAIN, |
602
|
|
|
is_ssl() |
603
|
|
|
); |
604
|
|
|
} |
605
|
|
|
} |
606
|
|
|
|
607
|
|
|
static function delete_connection_for_user( $user_id ) { |
608
|
|
|
if ( ! $wpcom_user_id = get_user_meta( $user_id, 'wpcom_user_id', true ) ) { |
609
|
|
|
return; |
610
|
|
|
} |
611
|
|
|
Jetpack::load_xml_rpc_client(); |
612
|
|
|
$xml = new Jetpack_IXR_Client( array( |
613
|
|
|
'wpcom_user_id' => $user_id, |
614
|
|
|
) ); |
615
|
|
|
$xml->query( 'jetpack.sso.removeUser', $wpcom_user_id ); |
616
|
|
|
|
617
|
|
|
if ( $xml->isError() ) { |
618
|
|
|
return false; |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
// Clean up local data stored for SSO |
622
|
|
|
delete_user_meta( $user_id, 'wpcom_user_id' ); |
623
|
|
|
delete_user_meta( $user_id, 'wpcom_user_data' ); |
624
|
|
|
self::clear_wpcom_profile_cookies(); |
625
|
|
|
|
626
|
|
|
return $xml->getResponse(); |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
static function request_initial_nonce() { |
630
|
|
|
$nonce = ! empty( $_COOKIE[ 'jetpack_sso_nonce' ] ) |
631
|
|
|
? $_COOKIE[ 'jetpack_sso_nonce' ] |
632
|
|
|
: false; |
633
|
|
|
|
634
|
|
|
if ( ! $nonce ) { |
635
|
|
|
Jetpack::load_xml_rpc_client(); |
636
|
|
|
$xml = new Jetpack_IXR_Client( array( |
637
|
|
|
'user_id' => get_current_user_id(), |
638
|
|
|
) ); |
639
|
|
|
$xml->query( 'jetpack.sso.requestNonce' ); |
640
|
|
|
|
641
|
|
|
if ( $xml->isError() ) { |
642
|
|
|
return new WP_Error( $xml->getErrorCode(), $xml->getErrorMessage() ); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
$nonce = $xml->getResponse(); |
646
|
|
|
|
647
|
|
|
setcookie( |
648
|
|
|
'jetpack_sso_nonce', |
649
|
|
|
$nonce, |
650
|
|
|
time() + ( 10 * MINUTE_IN_SECONDS ), |
651
|
|
|
COOKIEPATH, |
652
|
|
|
COOKIE_DOMAIN, |
653
|
|
|
is_ssl() |
654
|
|
|
); |
655
|
|
|
} |
656
|
|
|
|
657
|
|
|
return sanitize_key( $nonce ); |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
/** |
661
|
|
|
* The function that actually handles the login! |
662
|
|
|
*/ |
663
|
|
|
function handle_login() { |
664
|
|
|
$wpcom_nonce = sanitize_key( $_GET['sso_nonce'] ); |
665
|
|
|
$wpcom_user_id = (int) $_GET['user_id']; |
666
|
|
|
|
667
|
|
|
Jetpack::load_xml_rpc_client(); |
668
|
|
|
$xml = new Jetpack_IXR_Client( array( |
669
|
|
|
'user_id' => get_current_user_id(), |
670
|
|
|
) ); |
671
|
|
|
$xml->query( 'jetpack.sso.validateResult', $wpcom_nonce, $wpcom_user_id ); |
672
|
|
|
|
673
|
|
|
$user_data = $xml->isError() ? false : $xml->getResponse(); |
674
|
|
|
if ( empty( $user_data ) ) { |
675
|
|
|
add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' ); |
676
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_invalid_response_data' ) ); |
677
|
|
|
return; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
$user_data = (object) $user_data; |
681
|
|
|
$user = null; |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Fires before Jetpack's SSO modifies the log in form. |
685
|
|
|
* |
686
|
|
|
* @module sso |
687
|
|
|
* |
688
|
|
|
* @since 2.6.0 |
689
|
|
|
* |
690
|
|
|
* @param object $user_data WordPress.com User information. |
691
|
|
|
*/ |
692
|
|
|
do_action( 'jetpack_sso_pre_handle_login', $user_data ); |
693
|
|
|
|
694
|
|
|
if ( Jetpack_SSO_Helpers::is_two_step_required() && 0 === (int) $user_data->two_step_enabled ) { |
695
|
|
|
$this->user_data = $user_data; |
|
|
|
|
696
|
|
|
|
697
|
|
|
JetpackTracking::record_user_event( 'sso_login_failed', array( |
698
|
|
|
'error_message' => 'error_msg_enable_two_step' |
699
|
|
|
) ); |
700
|
|
|
|
701
|
|
|
/** This filter is documented in core/src/wp-includes/pluggable.php */ |
702
|
|
|
do_action( 'wp_login_failed', $user_data->login ); |
703
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_enable_two_step' ) ); |
704
|
|
|
return; |
705
|
|
|
} |
706
|
|
|
|
707
|
|
|
$user_found_with = ''; |
708
|
|
|
if ( empty( $user ) && isset( $user_data->external_user_id ) ) { |
709
|
|
|
$user_found_with = 'external_user_id'; |
710
|
|
|
$user = get_user_by( 'id', intval( $user_data->external_user_id ) ); |
711
|
|
|
if ( $user ) { |
712
|
|
|
update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID ); |
713
|
|
|
} |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
// If we don't have one by wpcom_user_id, try by the email? |
717
|
|
|
if ( empty( $user ) && Jetpack_SSO_Helpers::match_by_email() ) { |
718
|
|
|
$user_found_with = 'match_by_email'; |
719
|
|
|
$user = get_user_by( 'email', $user_data->email ); |
720
|
|
|
if ( $user ) { |
721
|
|
|
update_user_meta( $user->ID, 'wpcom_user_id', $user_data->ID ); |
722
|
|
|
} |
723
|
|
|
} |
724
|
|
|
|
725
|
|
|
// If we've still got nothing, create the user. |
726
|
|
|
$new_user_override_role = false; |
727
|
|
|
if ( empty( $user ) && ( get_option( 'users_can_register' ) || ( $new_user_override_role = Jetpack_SSO_Helpers::new_user_override( $user_data ) ) ) ) { |
728
|
|
|
/** |
729
|
|
|
* If not matching by email we still need to verify the email does not exist |
730
|
|
|
* or this blows up |
731
|
|
|
* |
732
|
|
|
* If match_by_email is true, we know the email doesn't exist, as it would have |
733
|
|
|
* been found in the first pass. If get_user_by( 'email' ) doesn't find the |
734
|
|
|
* user, then we know that email is unused, so it's safe to add. |
735
|
|
|
*/ |
736
|
|
|
if ( Jetpack_SSO_Helpers::match_by_email() || ! get_user_by( 'email', $user_data->email ) ) { |
737
|
|
|
|
738
|
|
|
if ( $new_user_override_role ) { |
739
|
|
|
$user_data->role = $new_user_override_role; |
740
|
|
|
} |
741
|
|
|
|
742
|
|
|
$user = Jetpack_SSO_Helpers::generate_user( $user_data ); |
743
|
|
|
if ( ! $user ) { |
744
|
|
|
JetpackTracking::record_user_event( 'sso_login_failed', array( |
745
|
|
|
'error_message' => 'could_not_create_username' |
746
|
|
|
) ); |
747
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'error_unable_to_create_user' ) ); |
748
|
|
|
return; |
749
|
|
|
} |
750
|
|
|
|
751
|
|
|
$user_found_with = $new_user_override_role |
752
|
|
|
? 'user_created_new_user_override' |
753
|
|
|
: 'user_created_users_can_register'; |
754
|
|
|
} else { |
755
|
|
|
JetpackTracking::record_user_event( 'sso_login_failed', array( |
756
|
|
|
'error_message' => 'error_msg_email_already_exists' |
757
|
|
|
) ); |
758
|
|
|
|
759
|
|
|
$this->user_data = $user_data; |
760
|
|
|
add_action( 'login_message', array( 'Jetpack_SSO_Notices', 'error_msg_email_already_exists' ) ); |
761
|
|
|
return; |
762
|
|
|
} |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
/** |
766
|
|
|
* Fires after we got login information from WordPress.com. |
767
|
|
|
* |
768
|
|
|
* @module sso |
769
|
|
|
* |
770
|
|
|
* @since 2.6.0 |
771
|
|
|
* |
772
|
|
|
* @param array $user Local User information. |
773
|
|
|
* @param object $user_data WordPress.com User Login information. |
774
|
|
|
*/ |
775
|
|
|
do_action( 'jetpack_sso_handle_login', $user, $user_data ); |
776
|
|
|
|
777
|
|
|
if ( $user ) { |
778
|
|
|
// Cache the user's details, so we can present it back to them on their user screen |
779
|
|
|
update_user_meta( $user->ID, 'wpcom_user_data', $user_data ); |
780
|
|
|
|
781
|
|
|
add_filter( 'auth_cookie_expiration', array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) ); |
782
|
|
|
wp_set_auth_cookie( $user->ID, true ); |
783
|
|
|
remove_filter( 'auth_cookie_expiration', array( 'Jetpack_SSO_Helpers', 'extend_auth_cookie_expiration_for_sso' ) ); |
784
|
|
|
|
785
|
|
|
/** This filter is documented in core/src/wp-includes/user.php */ |
786
|
|
|
do_action( 'wp_login', $user->user_login, $user ); |
787
|
|
|
|
788
|
|
|
wp_set_current_user( $user->ID ); |
789
|
|
|
|
790
|
|
|
$_request_redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : ''; |
791
|
|
|
$redirect_to = user_can( $user, 'edit_posts' ) ? admin_url() : self::profile_page_url(); |
792
|
|
|
|
793
|
|
|
// If we have a saved redirect to request in a cookie |
794
|
|
|
if ( ! empty( $_COOKIE['jetpack_sso_redirect_to'] ) ) { |
795
|
|
|
// Set that as the requested redirect to |
796
|
|
|
$redirect_to = $_request_redirect_to = esc_url_raw( $_COOKIE['jetpack_sso_redirect_to'] ); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
$json_api_auth_environment = Jetpack_SSO_Helpers::get_json_api_auth_environment(); |
800
|
|
|
|
801
|
|
|
$is_json_api_auth = ! empty( $json_api_auth_environment ); |
802
|
|
|
$is_user_connected = Jetpack::is_user_connected( $user->ID ); |
803
|
|
|
JetpackTracking::record_user_event( 'sso_user_logged_in', array( |
804
|
|
|
'user_found_with' => $user_found_with, |
805
|
|
|
'user_connected' => (bool) $is_user_connected, |
806
|
|
|
'user_role' => Jetpack::translate_current_user_to_role(), |
807
|
|
|
'is_json_api_auth' => (bool) $is_json_api_auth, |
808
|
|
|
) ); |
809
|
|
|
|
810
|
|
|
if ( $is_json_api_auth ) { |
811
|
|
|
Jetpack::init()->verify_json_api_authorization_request( $json_api_auth_environment ); |
812
|
|
|
Jetpack::init()->store_json_api_authorization_token( $user->user_login, $user ); |
813
|
|
|
|
814
|
|
|
} else if ( ! $is_user_connected ) { |
815
|
|
|
$calypso_env = ! empty( $_GET['calypso_env'] ) |
816
|
|
|
? sanitize_key( $_GET['calypso_env'] ) |
817
|
|
|
: ''; |
818
|
|
|
|
819
|
|
|
wp_safe_redirect( |
820
|
|
|
add_query_arg( |
821
|
|
|
array( |
822
|
|
|
'redirect_to' => $redirect_to, |
823
|
|
|
'request_redirect_to' => $_request_redirect_to, |
824
|
|
|
'calypso_env' => $calypso_env, |
825
|
|
|
'jetpack-sso-auth-redirect' => '1', |
826
|
|
|
), |
827
|
|
|
admin_url() |
828
|
|
|
) |
829
|
|
|
); |
830
|
|
|
exit; |
831
|
|
|
} |
832
|
|
|
|
833
|
|
|
add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) ); |
834
|
|
|
wp_safe_redirect( |
835
|
|
|
/** This filter is documented in core/src/wp-login.php */ |
836
|
|
|
apply_filters( 'login_redirect', $redirect_to, $_request_redirect_to, $user ) |
837
|
|
|
); |
838
|
|
|
exit; |
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
add_filter( 'jetpack_sso_default_to_sso_login', '__return_false' ); |
842
|
|
|
|
843
|
|
|
JetpackTracking::record_user_event( 'sso_login_failed', array( |
844
|
|
|
'error_message' => 'cant_find_user' |
845
|
|
|
) ); |
846
|
|
|
|
847
|
|
|
$this->user_data = $user_data; |
848
|
|
|
/** This filter is documented in core/src/wp-includes/pluggable.php */ |
849
|
|
|
do_action( 'wp_login_failed', $user_data->login ); |
850
|
|
|
add_filter( 'login_message', array( 'Jetpack_SSO_Notices', 'cant_find_user' ) ); |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
static function profile_page_url() { |
854
|
|
|
return admin_url( 'profile.php' ); |
855
|
|
|
} |
856
|
|
|
|
857
|
|
|
/** |
858
|
|
|
* Builds the "Login to WordPress.com" button that is displayed on the login page as well as user profile page. |
859
|
|
|
* |
860
|
|
|
* @param array $args An array of arguments to add to the SSO URL. |
861
|
|
|
* @param boolean $is_primary Should the button have the `button-primary` class? |
862
|
|
|
* @return string Returns the HTML markup for the button. |
863
|
|
|
*/ |
864
|
|
|
function build_sso_button( $args = array(), $is_primary = false ) { |
865
|
|
|
$url = $this->build_sso_button_url( $args ); |
866
|
|
|
$classes = $is_primary |
867
|
|
|
? 'jetpack-sso button button-primary' |
868
|
|
|
: 'jetpack-sso button'; |
869
|
|
|
|
870
|
|
|
return sprintf( |
871
|
|
|
'<a rel="nofollow" href="%1$s" class="%2$s"><span>%3$s %4$s</span></a>', |
872
|
|
|
esc_url( $url ), |
873
|
|
|
$classes, |
874
|
|
|
'<span class="genericon genericon-wordpress"></span>', |
875
|
|
|
esc_html__( 'Log in with WordPress.com', 'jetpack' ) |
876
|
|
|
); |
877
|
|
|
} |
878
|
|
|
|
879
|
|
|
/** |
880
|
|
|
* Builds a URL with `jetpack-sso` action and option args which is used to setup SSO. |
881
|
|
|
* |
882
|
|
|
* @param array $args An array of arguments to add to the SSO URL. |
883
|
|
|
* @return string The URL used for SSO. |
884
|
|
|
*/ |
885
|
|
|
function build_sso_button_url( $args = array() ) { |
886
|
|
|
$defaults = array( |
887
|
|
|
'action' => 'jetpack-sso', |
888
|
|
|
); |
889
|
|
|
|
890
|
|
|
$args = wp_parse_args( $args, $defaults ); |
891
|
|
|
|
892
|
|
|
if ( ! empty( $_GET['redirect_to'] ) ) { |
893
|
|
|
$args['redirect_to'] = urlencode( esc_url_raw( $_GET['redirect_to'] ) ); |
894
|
|
|
} |
895
|
|
|
|
896
|
|
|
return add_query_arg( $args, wp_login_url() ); |
897
|
|
|
} |
898
|
|
|
|
899
|
|
|
/** |
900
|
|
|
* Retrieves a WordPress.com SSO URL with appropriate query parameters or dies. |
901
|
|
|
* |
902
|
|
|
* @param boolean $reauth Should the user be forced to reauthenticate on WordPress.com? |
903
|
|
|
* @param array $args Optional query parameters. |
904
|
|
|
* @return string The WordPress.com SSO URL. |
905
|
|
|
*/ |
906
|
|
|
function get_sso_url_or_die( $reauth = false, $args = array() ) { |
907
|
|
|
if ( empty( $reauth ) ) { |
908
|
|
|
$sso_redirect = $this->build_sso_url( $args ); |
909
|
|
|
} else { |
910
|
|
|
self::clear_wpcom_profile_cookies(); |
911
|
|
|
$sso_redirect = $this->build_reauth_and_sso_url( $args ); |
912
|
|
|
} |
913
|
|
|
|
914
|
|
|
// If there was an error retrieving the SSO URL, then error. |
915
|
|
|
if ( is_wp_error( $sso_redirect ) ) { |
916
|
|
|
$error_message = sanitize_text_field( |
917
|
|
|
sprintf( '%s: %s', $sso_redirect->get_error_code(), $sso_redirect->get_error_message() ) |
|
|
|
|
918
|
|
|
); |
919
|
|
|
JetpackTracking::record_user_event( 'sso_login_redirect_failed', array( |
920
|
|
|
'error_message' => $error_message |
921
|
|
|
) ); |
922
|
|
|
wp_die( $error_message ); |
923
|
|
|
} |
924
|
|
|
|
925
|
|
|
return $sso_redirect; |
926
|
|
|
} |
927
|
|
|
|
928
|
|
|
/** |
929
|
|
|
* Build WordPress.com SSO URL with appropriate query parameters. |
930
|
|
|
* |
931
|
|
|
* @param array $args Optional query parameters. |
932
|
|
|
* @return string WordPress.com SSO URL |
933
|
|
|
*/ |
934
|
|
|
function build_sso_url( $args = array() ) { |
935
|
|
|
$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce(); |
936
|
|
|
$defaults = array( |
937
|
|
|
'action' => 'jetpack-sso', |
938
|
|
|
'site_id' => Jetpack_Options::get_option( 'id' ), |
939
|
|
|
'sso_nonce' => $sso_nonce, |
940
|
|
|
'calypso_auth' => '1', |
941
|
|
|
); |
942
|
|
|
|
943
|
|
|
$args = wp_parse_args( $args, $defaults ); |
944
|
|
|
|
945
|
|
|
if ( is_wp_error( $args['sso_nonce'] ) ) { |
946
|
|
|
return $args['sso_nonce']; |
947
|
|
|
} |
948
|
|
|
|
949
|
|
|
return add_query_arg( $args, 'https://wordpress.com/wp-login.php' ); |
950
|
|
|
} |
951
|
|
|
|
952
|
|
|
/** |
953
|
|
|
* Build WordPress.com SSO URL with appropriate query parameters, |
954
|
|
|
* including the parameters necessary to force the user to reauthenticate |
955
|
|
|
* on WordPress.com. |
956
|
|
|
* |
957
|
|
|
* @param array $args Optional query parameters. |
958
|
|
|
* @return string WordPress.com SSO URL |
959
|
|
|
*/ |
960
|
|
|
function build_reauth_and_sso_url( $args = array() ) { |
961
|
|
|
$sso_nonce = ! empty( $args['sso_nonce'] ) ? $args['sso_nonce'] : self::request_initial_nonce(); |
962
|
|
|
$redirect = $this->build_sso_url( array( 'force_auth' => '1', 'sso_nonce' => $sso_nonce ) ); |
963
|
|
|
|
964
|
|
|
if ( is_wp_error( $redirect ) ) { |
965
|
|
|
return $redirect; |
966
|
|
|
} |
967
|
|
|
|
968
|
|
|
$defaults = array( |
969
|
|
|
'action' => 'jetpack-sso', |
970
|
|
|
'site_id' => Jetpack_Options::get_option( 'id' ), |
971
|
|
|
'sso_nonce' => $sso_nonce, |
972
|
|
|
'reauth' => '1', |
973
|
|
|
'redirect_to' => urlencode( $redirect ), |
974
|
|
|
'calypso_auth' => '1', |
975
|
|
|
); |
976
|
|
|
|
977
|
|
|
$args = wp_parse_args( $args, $defaults ); |
978
|
|
|
|
979
|
|
|
if ( is_wp_error( $args['sso_nonce'] ) ) { |
980
|
|
|
return $args['sso_nonce']; |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
return add_query_arg( $args, 'https://wordpress.com/wp-login.php' ); |
984
|
|
|
} |
985
|
|
|
|
986
|
|
|
/** |
987
|
|
|
* Determines local user associated with a given WordPress.com user ID. |
988
|
|
|
* |
989
|
|
|
* @since 2.6.0 |
990
|
|
|
* |
991
|
|
|
* @param int $wpcom_user_id User ID from WordPress.com |
992
|
|
|
* @return object Local user object if found, null if not. |
993
|
|
|
*/ |
994
|
|
|
static function get_user_by_wpcom_id( $wpcom_user_id ) { |
995
|
|
|
$user_query = new WP_User_Query( array( |
996
|
|
|
'meta_key' => 'wpcom_user_id', |
997
|
|
|
'meta_value' => intval( $wpcom_user_id ), |
998
|
|
|
'number' => 1, |
999
|
|
|
) ); |
1000
|
|
|
|
1001
|
|
|
$users = $user_query->get_results(); |
1002
|
|
|
return $users ? array_shift( $users ) : null; |
1003
|
|
|
} |
1004
|
|
|
|
1005
|
|
|
/** |
1006
|
|
|
* When jetpack-sso-auth-redirect query parameter is set, will redirect user to |
1007
|
|
|
* WordPress.com authorization flow. |
1008
|
|
|
* |
1009
|
|
|
* We redirect here instead of in handle_login() because Jetpack::init()->build_connect_url |
1010
|
|
|
* calls menu_page_url() which doesn't work properly until admin menus are registered. |
1011
|
|
|
*/ |
1012
|
|
|
function maybe_authorize_user_after_sso() { |
1013
|
|
|
if ( empty( $_GET['jetpack-sso-auth-redirect'] ) ) { |
1014
|
|
|
return; |
1015
|
|
|
} |
1016
|
|
|
|
1017
|
|
|
$redirect_to = ! empty( $_GET['redirect_to'] ) ? esc_url_raw( $_GET['redirect_to'] ) : admin_url(); |
1018
|
|
|
$request_redirect_to = ! empty( $_GET['request_redirect_to'] ) ? esc_url_raw( $_GET['request_redirect_to'] ) : $redirect_to; |
1019
|
|
|
|
1020
|
|
|
/** This filter is documented in core/src/wp-login.php */ |
1021
|
|
|
$redirect_after_auth = apply_filters( 'login_redirect', $redirect_to, $request_redirect_to, wp_get_current_user() ); |
1022
|
|
|
|
1023
|
|
|
/** |
1024
|
|
|
* Since we are passing this redirect to WordPress.com and therefore can not use wp_safe_redirect(), |
1025
|
|
|
* let's sanitize it here to make sure it's safe. If the redirect is not safe, then use admin_url(). |
1026
|
|
|
*/ |
1027
|
|
|
$redirect_after_auth = wp_sanitize_redirect( $redirect_after_auth ); |
1028
|
|
|
$redirect_after_auth = wp_validate_redirect( $redirect_after_auth, admin_url() ); |
1029
|
|
|
|
1030
|
|
|
/** |
1031
|
|
|
* Return the raw connect URL with our redirect and attribute connection to SSO. |
1032
|
|
|
*/ |
1033
|
|
|
$connect_url = Jetpack::init()->build_connect_url( true, $redirect_after_auth, 'sso' ); |
1034
|
|
|
|
1035
|
|
|
add_filter( 'allowed_redirect_hosts', array( 'Jetpack_SSO_Helpers', 'allowed_redirect_hosts' ) ); |
1036
|
|
|
wp_safe_redirect( $connect_url ); |
1037
|
|
|
exit; |
1038
|
|
|
} |
1039
|
|
|
|
1040
|
|
|
/** |
1041
|
|
|
* Cache user's display name and Gravatar so it can be displayed on the login screen. These cookies are |
1042
|
|
|
* stored when the user logs out, and then deleted when the user logs in. |
1043
|
|
|
*/ |
1044
|
|
|
function store_wpcom_profile_cookies_on_logout() { |
1045
|
|
|
if ( ! Jetpack::is_user_connected( get_current_user_id() ) ) { |
1046
|
|
|
return; |
1047
|
|
|
} |
1048
|
|
|
|
1049
|
|
|
$user_data = $this->get_user_data( get_current_user_id() ); |
1050
|
|
|
if ( ! $user_data ) { |
1051
|
|
|
return; |
1052
|
|
|
} |
1053
|
|
|
|
1054
|
|
|
setcookie( |
1055
|
|
|
'jetpack_sso_wpcom_name_' . COOKIEHASH, |
1056
|
|
|
$user_data->display_name, |
1057
|
|
|
time() + WEEK_IN_SECONDS, |
1058
|
|
|
COOKIEPATH, |
1059
|
|
|
COOKIE_DOMAIN, |
1060
|
|
|
is_ssl() |
1061
|
|
|
); |
1062
|
|
|
|
1063
|
|
|
setcookie( |
1064
|
|
|
'jetpack_sso_wpcom_gravatar_' . COOKIEHASH, |
1065
|
|
|
get_avatar_url( |
1066
|
|
|
$user_data->email, |
1067
|
|
|
array( 'size' => 144, 'default' => 'mystery' ) |
1068
|
|
|
), |
1069
|
|
|
time() + WEEK_IN_SECONDS, |
1070
|
|
|
COOKIEPATH, |
1071
|
|
|
COOKIE_DOMAIN, |
1072
|
|
|
is_ssl() |
1073
|
|
|
); |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
/** |
1077
|
|
|
* Determines if a local user is connected to WordPress.com |
1078
|
|
|
* |
1079
|
|
|
* @since 2.8 |
1080
|
|
|
* @param integer $user_id - Local user id |
1081
|
|
|
* @return boolean |
1082
|
|
|
**/ |
1083
|
|
|
public function is_user_connected( $user_id ) { |
1084
|
|
|
return $this->get_user_data( $user_id ); |
1085
|
|
|
} |
1086
|
|
|
|
1087
|
|
|
/** |
1088
|
|
|
* Retrieves a user's WordPress.com data |
1089
|
|
|
* |
1090
|
|
|
* @since 2.8 |
1091
|
|
|
* @param integer $user_id - Local user id |
1092
|
|
|
* @return mixed null or stdClass |
1093
|
|
|
**/ |
1094
|
|
|
public function get_user_data( $user_id ) { |
1095
|
|
|
return get_user_meta( $user_id, 'wpcom_user_data', true ); |
1096
|
|
|
} |
1097
|
|
|
|
1098
|
|
|
/** |
1099
|
|
|
* Mark SSO as discovered when an SSO JITM is viewed. |
1100
|
|
|
* |
1101
|
|
|
* @since 6.9.0 |
1102
|
|
|
* |
1103
|
|
|
* @param array $envelopes Array of JITM messages received after API call. |
1104
|
|
|
* |
1105
|
|
|
* @return array $envelopes New array of JITM messages. May now contain only one message, about SSO. |
1106
|
|
|
*/ |
1107
|
|
|
public function inject_sso_jitm( $envelopes ) { |
1108
|
|
|
// Bail early if that's not the first time the user uses SSO. |
1109
|
|
|
if ( true != Jetpack_Options::get_option( 'sso_first_login' ) ) { |
1110
|
|
|
return $envelopes; |
1111
|
|
|
} |
1112
|
|
|
|
1113
|
|
|
// Update our option to mark that SSO was discovered. |
1114
|
|
|
Jetpack_Options::update_option( 'sso_first_login', false ); |
1115
|
|
|
|
1116
|
|
|
return $this->prepare_sso_first_login_jitm(); |
1117
|
|
|
} |
1118
|
|
|
|
1119
|
|
|
/** |
1120
|
|
|
* Prepare JITM array for new SSO users |
1121
|
|
|
* |
1122
|
|
|
* @since 6.9.0 |
1123
|
|
|
* |
1124
|
|
|
* @return array $sso_first_login_jitm array containting one object of information about our message. |
1125
|
|
|
*/ |
1126
|
|
|
private function prepare_sso_first_login_jitm() { |
1127
|
|
|
// Build our custom SSO JITM. |
1128
|
|
|
$discover_sso_message = array( |
1129
|
|
|
'content' => array( |
1130
|
|
|
'message' => esc_html__( "You've successfully signed in with WordPress.com Secure Sign On!", 'jetpack' ), |
1131
|
|
|
'icon' => 'jetpack', |
1132
|
|
|
'list' => array(), |
1133
|
|
|
'description' => esc_html__( 'Interested in learning more about how Secure Sign on keeps your site safer?', 'jetpack' ), |
1134
|
|
|
'classes' => '', |
1135
|
|
|
), |
1136
|
|
|
'CTA' => array( |
1137
|
|
|
'message' => esc_html__( 'Learn More', 'jetpack' ), |
1138
|
|
|
'hook' => '', |
1139
|
|
|
'newWindow' => true, |
1140
|
|
|
'primary' => true, |
1141
|
|
|
), |
1142
|
|
|
'template' => 'default', |
1143
|
|
|
'ttl' => 300, |
1144
|
|
|
'id' => 'sso_discover', |
1145
|
|
|
'feature_class' => 'sso', |
1146
|
|
|
'expires' => 3628800, |
1147
|
|
|
'max_dismissal' => 1, |
1148
|
|
|
'activate_module' => null, |
1149
|
|
|
); |
1150
|
|
|
|
1151
|
|
|
return array( json_decode( json_encode( $discover_sso_message ) ) ); |
1152
|
|
|
} |
1153
|
|
|
} |
1154
|
|
|
|
1155
|
|
|
Jetpack_SSO::get_instance(); |
1156
|
|
|
|
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.