1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
use Automattic\Jetpack\Tracking; |
4
|
|
|
/** |
5
|
|
|
* Just a sack of functions. Not actually an IXR_Server |
6
|
|
|
*/ |
7
|
|
|
class Jetpack_XMLRPC_Server { |
8
|
|
|
/** |
9
|
|
|
* The current error object |
10
|
|
|
*/ |
11
|
|
|
public $error = null; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* The current user |
15
|
|
|
*/ |
16
|
|
|
public $user = null; |
17
|
|
|
|
18
|
|
|
private $tracking; |
19
|
|
|
|
20
|
|
|
function __construct() { |
21
|
|
|
$this->tracking = new Tracking(); |
22
|
|
|
} |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Whitelist of the XML-RPC methods available to the Jetpack Server. If the |
26
|
|
|
* user is not authenticated (->login()) then the methods are never added, |
27
|
|
|
* so they will get a "does not exist" error. |
28
|
|
|
*/ |
29
|
|
|
function xmlrpc_methods( $core_methods ) { |
30
|
|
|
$jetpack_methods = array( |
31
|
|
|
'jetpack.jsonAPI' => array( $this, 'json_api' ), |
32
|
|
|
'jetpack.verifyAction' => array( $this, 'verify_action' ), |
33
|
|
|
'jetpack.remoteRegister' => array( $this, 'remote_register' ), |
34
|
|
|
'jetpack.remoteProvision' => array( $this, 'remote_provision' ), |
35
|
|
|
); |
36
|
|
|
|
37
|
|
|
$this->user = $this->login(); |
38
|
|
|
|
39
|
|
|
if ( $this->user ) { |
40
|
|
|
$jetpack_methods = array_merge( $jetpack_methods, array( |
41
|
|
|
'jetpack.testConnection' => array( $this, 'test_connection' ), |
42
|
|
|
'jetpack.testAPIUserCode' => array( $this, 'test_api_user_code' ), |
43
|
|
|
'jetpack.featuresAvailable' => array( $this, 'features_available' ), |
44
|
|
|
'jetpack.featuresEnabled' => array( $this, 'features_enabled' ), |
45
|
|
|
'jetpack.disconnectBlog' => array( $this, 'disconnect_blog' ), |
46
|
|
|
'jetpack.unlinkUser' => array( $this, 'unlink_user' ), |
47
|
|
|
'jetpack.syncObject' => array( $this, 'sync_object' ), |
48
|
|
|
'jetpack.idcUrlValidation' => array( $this, 'validate_urls_for_idc_mitigation' ), |
49
|
|
|
) ); |
50
|
|
|
|
51
|
|
|
if ( isset( $core_methods['metaWeblog.editPost'] ) ) { |
52
|
|
|
$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject']; |
53
|
|
|
$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' ); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Filters the XML-RPC methods available to Jetpack for authenticated users. |
58
|
|
|
* |
59
|
|
|
* @since 1.1.0 |
60
|
|
|
* |
61
|
|
|
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. |
62
|
|
|
* @param array $core_methods Available core XML-RPC methods. |
63
|
|
|
* @param WP_User $user Information about a given WordPress user. |
64
|
|
|
*/ |
65
|
|
|
$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user ); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Filters the XML-RPC methods available to Jetpack for unauthenticated users. |
70
|
|
|
* |
71
|
|
|
* @since 3.0.0 |
72
|
|
|
* |
73
|
|
|
* @param array $jetpack_methods XML-RPC methods available to the Jetpack Server. |
74
|
|
|
* @param array $core_methods Available core XML-RPC methods. |
75
|
|
|
*/ |
76
|
|
|
return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods ); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Whitelist of the bootstrap XML-RPC methods |
81
|
|
|
*/ |
82
|
|
|
function bootstrap_xmlrpc_methods() { |
83
|
|
|
return array( |
84
|
|
|
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ), |
85
|
|
|
'jetpack.remoteRegister' => array( $this, 'remote_register' ), |
86
|
|
|
); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
function authorize_xmlrpc_methods() { |
90
|
|
|
return array( |
91
|
|
|
'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ), |
92
|
|
|
); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
function provision_xmlrpc_methods() { |
96
|
|
|
return array( |
97
|
|
|
'jetpack.remoteRegister' => array( $this, 'remote_register' ), |
98
|
|
|
'jetpack.remoteProvision' => array( $this, 'remote_provision' ), |
99
|
|
|
'jetpack.remoteConnect' => array( $this, 'remote_connect' ), |
100
|
|
|
); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
function remote_authorize( $request ) { |
104
|
|
|
$user = get_user_by( 'id', $request['state'] ); |
105
|
|
|
$this->tracking->record_user_event( 'jpc_remote_authorize_begin', array(), $user ); |
106
|
|
|
|
107
|
|
|
foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) { |
108
|
|
|
if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) { |
109
|
|
|
return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' ); |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
if ( ! $user ) { |
114
|
|
|
return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' ); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) { |
118
|
|
|
return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' ); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) ); |
122
|
|
|
|
123
|
|
|
if ( is_a( $verified, 'IXR_Error' ) ) { |
124
|
|
|
return $this->error( $verified, 'jpc_remote_authorize_fail' ); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
wp_set_current_user( $request['state'] ); |
128
|
|
|
|
129
|
|
|
$client_server = new Jetpack_Client_Server; |
130
|
|
|
$result = $client_server->authorize( $request ); |
131
|
|
|
|
132
|
|
|
if ( is_wp_error( $result ) ) { |
133
|
|
|
return $this->error( $result, 'jpc_remote_authorize_fail' ); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$this->tracking->record_user_event( 'jpc_remote_authorize_success' ); |
137
|
|
|
|
138
|
|
|
return array( |
139
|
|
|
'result' => $result, |
140
|
|
|
); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to |
145
|
|
|
* register this site so that a plan can be provisioned. |
146
|
|
|
* |
147
|
|
|
* @param array $request An array containing at minimum nonce and local_user keys. |
148
|
|
|
* |
149
|
|
|
* @return WP_Error|array |
150
|
|
|
*/ |
151
|
|
|
public function remote_register( $request ) { |
152
|
|
|
$this->tracking->record_user_event( 'jpc_remote_register_begin', array() ); |
153
|
|
|
|
154
|
|
|
$user = $this->fetch_and_verify_local_user( $request ); |
155
|
|
|
|
156
|
|
|
if ( ! $user ) { |
157
|
|
|
return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' ); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) { |
161
|
|
|
return $this->error( $user, 'jpc_remote_register_fail' ); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
if ( empty( $request['nonce'] ) ) { |
165
|
|
|
return $this->error( |
166
|
|
|
new Jetpack_Error( |
167
|
|
|
'nonce_missing', |
168
|
|
|
__( 'The required "nonce" parameter is missing.', 'jetpack' ), |
169
|
|
|
400 |
170
|
|
|
), |
171
|
|
|
'jpc_remote_register_fail' |
172
|
|
|
); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
$nonce = sanitize_text_field( $request['nonce'] ); |
176
|
|
|
unset( $request['nonce'] ); |
177
|
|
|
|
178
|
|
|
$api_url = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) ); |
179
|
|
|
$response = Jetpack_Client::_wp_remote_request( |
180
|
|
|
esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ), |
181
|
|
|
array( 'method' => 'GET' ), |
182
|
|
|
true |
183
|
|
|
); |
184
|
|
|
|
185
|
|
|
if ( |
186
|
|
|
200 !== wp_remote_retrieve_response_code( $response ) || |
187
|
|
|
'OK' !== trim( wp_remote_retrieve_body( $response ) ) |
188
|
|
|
) { |
189
|
|
|
return $this->error( |
190
|
|
|
new Jetpack_Error( |
191
|
|
|
'invalid_nonce', |
192
|
|
|
__( 'There was an issue validating this request.', 'jetpack' ), |
193
|
|
|
400 |
194
|
|
|
), |
195
|
|
|
'jpc_remote_register_fail' |
196
|
|
|
); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Data::get_access_token() || ! empty( $request['force'] ) ) { |
|
|
|
|
200
|
|
|
wp_set_current_user( $user->ID ); |
201
|
|
|
|
202
|
|
|
// This code mostly copied from Jetpack::admin_page_load. |
203
|
|
|
Jetpack::maybe_set_version_option(); |
204
|
|
|
$registered = Jetpack::try_registration(); |
205
|
|
|
if ( is_wp_error( $registered ) ) { |
206
|
|
|
return $this->error( $registered, 'jpc_remote_register_fail' ); |
207
|
|
|
} elseif ( ! $registered ) { |
208
|
|
|
return $this->error( |
209
|
|
|
new Jetpack_Error( |
210
|
|
|
'registration_error', |
211
|
|
|
__( 'There was an unspecified error registering the site', 'jetpack' ), |
212
|
|
|
400 |
213
|
|
|
), |
214
|
|
|
'jpc_remote_register_fail' |
215
|
|
|
); |
216
|
|
|
} |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$this->tracking->record_user_event( 'jpc_remote_register_success' ); |
220
|
|
|
|
221
|
|
|
return array( |
222
|
|
|
'client_id' => Jetpack_Options::get_option( 'id' ) |
223
|
|
|
); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to |
228
|
|
|
* register this site so that a plan can be provisioned. |
229
|
|
|
* |
230
|
|
|
* @param array $request An array containing at minimum a nonce key and a local_username key. |
231
|
|
|
* |
232
|
|
|
* @return WP_Error|array |
233
|
|
|
*/ |
234
|
|
|
public function remote_provision( $request ) { |
235
|
|
|
$user = $this->fetch_and_verify_local_user( $request ); |
236
|
|
|
|
237
|
|
|
if ( ! $user ) { |
238
|
|
|
return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' ); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) { |
242
|
|
|
return $this->error( $user, 'jpc_remote_provision_fail' ); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
$site_icon = get_site_icon_url(); |
246
|
|
|
|
247
|
|
|
$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) ); |
248
|
|
|
|
249
|
|
|
/** This filter is documented in class.jetpack-cli.php */ |
250
|
|
View Code Duplication |
if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) { |
251
|
|
|
$redirect_uri = add_query_arg( |
252
|
|
|
array( |
253
|
|
|
'action' => 'jetpack-sso', |
254
|
|
|
'redirect_to' => rawurlencode( admin_url() ), |
255
|
|
|
), |
256
|
|
|
wp_login_url() // TODO: come back to Jetpack dashboard? |
257
|
|
|
); |
258
|
|
|
} else { |
259
|
|
|
$redirect_uri = admin_url(); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
// Generate secrets. |
263
|
|
|
$role = Jetpack::translate_user_to_role( $user ); |
264
|
|
|
$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID ); |
265
|
|
|
|
266
|
|
|
$response = array( |
267
|
|
|
'jp_version' => JETPACK__VERSION, |
268
|
|
|
'redirect_uri' => $redirect_uri, |
269
|
|
|
'user_id' => $user->ID, |
270
|
|
|
'user_email' => $user->user_email, |
271
|
|
|
'user_login' => $user->user_login, |
272
|
|
|
'scope' => Jetpack::sign_role( $role, $user->ID ), |
273
|
|
|
'secret' => $secrets['secret_1'], |
274
|
|
|
'is_active' => Jetpack::is_active(), |
275
|
|
|
); |
276
|
|
|
|
277
|
|
|
if ( $site_icon ) { |
278
|
|
|
$response['site_icon'] = $site_icon; |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
if ( ! empty( $request['onboarding'] ) ) { |
282
|
|
|
Jetpack::create_onboarding_token(); |
283
|
|
|
$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' ); |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
return $response; |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Given an array containing a local user identifier and a nonce, will attempt to fetch and set |
291
|
|
|
* an access token for the given user. |
292
|
|
|
* |
293
|
|
|
* @param array $request An array containing local_user and nonce keys at minimum. |
294
|
|
|
* @return mixed |
295
|
|
|
*/ |
296
|
|
|
public function remote_connect( $request, $ixr_client = false ) { |
297
|
|
|
if ( Jetpack::is_active() ) { |
298
|
|
|
return $this->error( |
299
|
|
|
new WP_Error( |
300
|
|
|
'already_connected', |
301
|
|
|
__( 'Jetpack is already connected.', 'jetpack' ), |
302
|
|
|
400 |
303
|
|
|
), |
304
|
|
|
'jpc_remote_connect_fail' |
305
|
|
|
); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$user = $this->fetch_and_verify_local_user( $request ); |
309
|
|
|
|
310
|
|
|
if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) { |
311
|
|
|
return $this->error( |
312
|
|
|
new WP_Error( |
313
|
|
|
'input_error', |
314
|
|
|
__( 'Valid user is required.', 'jetpack' ), |
315
|
|
|
400 |
316
|
|
|
), |
317
|
|
|
'jpc_remote_connect_fail' |
318
|
|
|
); |
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
if ( empty( $request['nonce'] ) ) { |
322
|
|
|
return $this->error( |
323
|
|
|
new WP_Error( |
324
|
|
|
'input_error', |
325
|
|
|
__( 'A non-empty nonce must be supplied.', 'jetpack' ), |
326
|
|
|
400 |
327
|
|
|
), |
328
|
|
|
'jpc_remote_connect_fail' |
329
|
|
|
); |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
if ( ! $ixr_client ) { |
333
|
|
|
Jetpack::load_xml_rpc_client(); |
334
|
|
|
$ixr_client = new Jetpack_IXR_Client(); |
335
|
|
|
} |
336
|
|
|
$ixr_client->query( 'jetpack.getUserAccessToken', array( |
|
|
|
|
337
|
|
|
'nonce' => sanitize_text_field( $request['nonce'] ), |
338
|
|
|
'external_user_id' => $user->ID, |
339
|
|
|
) ); |
340
|
|
|
|
341
|
|
|
$token = $ixr_client->isError() ? false : $ixr_client->getResponse(); |
342
|
|
|
if ( empty( $token ) ) { |
343
|
|
|
return $this->error( |
344
|
|
|
new WP_Error( |
345
|
|
|
'token_fetch_failed', |
346
|
|
|
__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ), |
347
|
|
|
400 |
348
|
|
|
), |
349
|
|
|
'jpc_remote_connect_fail' |
350
|
|
|
); |
351
|
|
|
} |
352
|
|
|
$token = sanitize_text_field( $token ); |
353
|
|
|
|
354
|
|
|
Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true ); |
355
|
|
|
|
356
|
|
|
$this->do_post_authorization(); |
357
|
|
|
|
358
|
|
|
return Jetpack::is_active(); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
private function fetch_and_verify_local_user( $request ) { |
362
|
|
|
if ( empty( $request['local_user'] ) ) { |
363
|
|
|
return $this->error( |
364
|
|
|
new Jetpack_Error( |
365
|
|
|
'local_user_missing', |
366
|
|
|
__( 'The required "local_user" parameter is missing.', 'jetpack' ), |
367
|
|
|
400 |
368
|
|
|
), |
369
|
|
|
'jpc_remote_provision_fail' |
370
|
|
|
); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
// local user is used to look up by login, email or ID |
374
|
|
|
$local_user_info = $request['local_user']; |
375
|
|
|
|
376
|
|
|
$user = get_user_by( 'login', $local_user_info ); |
377
|
|
|
|
378
|
|
|
if ( ! $user ) { |
379
|
|
|
$user = get_user_by( 'email', $local_user_info ); |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
if ( ! $user ) { |
383
|
|
|
$user = get_user_by( 'ID', $local_user_info ); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
return $user; |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
private function tracks_record_error( $name, $error, $user = null ) { |
390
|
|
|
if ( is_wp_error( $error ) ) { |
391
|
|
|
$this->tracking->record_user_event( $name, array( |
392
|
|
|
'error_code' => $error->get_error_code(), |
393
|
|
|
'error_message' => $error->get_error_message() |
394
|
|
|
), $user ); |
395
|
|
|
} elseif( is_a( $error, 'IXR_Error' ) ) { |
396
|
|
|
$this->tracking->record_user_event( $name, array( |
397
|
|
|
'error_code' => $error->code, |
398
|
|
|
'error_message' => $error->message |
399
|
|
|
), $user ); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
return $error; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure |
407
|
|
|
* |
408
|
|
|
* Possible error_codes: |
409
|
|
|
* |
410
|
|
|
* verify_secret_1_missing |
411
|
|
|
* verify_secret_1_malformed |
412
|
|
|
* verify_secrets_missing: verification secrets are not found in database |
413
|
|
|
* verify_secrets_incomplete: verification secrets are only partially found in database |
414
|
|
|
* verify_secrets_expired: verification secrets have expired |
415
|
|
|
* verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com |
416
|
|
|
* state_missing: required parameter of state not found |
417
|
|
|
* state_malformed: state is not a digit |
418
|
|
|
* invalid_state: state in request does not match the stored state |
419
|
|
|
* |
420
|
|
|
* The 'authorize' and 'register' actions have additional error codes |
421
|
|
|
* |
422
|
|
|
* Possible values for action are `authorize`, `publicize` and `register`. |
423
|
|
|
* |
424
|
|
|
* state_missing: a state ( user id ) was not supplied |
425
|
|
|
* state_malformed: state is not the correct data type |
426
|
|
|
* invalid_state: supplied state does not match the stored state |
427
|
|
|
*/ |
428
|
|
|
function verify_action( $params ) { |
429
|
|
|
$action = $params[0]; |
430
|
|
|
$verify_secret = $params[1]; |
431
|
|
|
$state = isset( $params[2] ) ? $params[2] : ''; |
432
|
|
|
$user = get_user_by( 'id', $state ); |
433
|
|
|
$tracks_failure_event_name = ''; |
434
|
|
|
|
435
|
|
|
if ( 'authorize' === $action ) { |
436
|
|
|
$tracks_failure_event_name = 'jpc_verify_authorize_fail'; |
437
|
|
|
$this->tracking->record_user_event( 'jpc_verify_authorize_begin', array(), $user ); |
438
|
|
|
} |
439
|
|
|
if ( 'publicize' === $action ) { |
440
|
|
|
// This action is used on a response from a direct XML-RPC done from WordPress.com |
441
|
|
|
$tracks_failure_event_name = 'jpc_verify_publicize_fail'; |
442
|
|
|
$this->tracking->record_user_event( 'jpc_verify_publicize_begin', array(), $user ); |
443
|
|
|
} |
444
|
|
|
if ( 'register' === $action ) { |
445
|
|
|
$tracks_failure_event_name = 'jpc_verify_register_fail'; |
446
|
|
|
$this->tracking->record_user_event( 'jpc_verify_register_begin', array(), $user ); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
if ( empty( $verify_secret ) ) { |
450
|
|
|
return $this->error( new Jetpack_Error( 'verify_secret_1_missing', sprintf( 'The required "%s" parameter is missing.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user ); |
451
|
|
|
} else if ( ! is_string( $verify_secret ) ) { |
452
|
|
|
return $this->error( new Jetpack_Error( 'verify_secret_1_malformed', sprintf( 'The required "%s" parameter is malformed.', 'secret_1' ), 400 ), $tracks_failure_event_name, $user ); |
453
|
|
|
} else if ( empty( $state ) ) { |
454
|
|
|
return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user ); |
455
|
|
|
} else if ( ! ctype_digit( $state ) ) { |
456
|
|
|
return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user ); |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
$secrets = Jetpack::get_secrets( $action, $state ); |
460
|
|
|
|
461
|
|
|
if ( ! $secrets ) { |
462
|
|
|
Jetpack::delete_secrets( $action, $state ); |
|
|
|
|
463
|
|
|
return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user ); |
464
|
|
|
} |
465
|
|
|
|
466
|
|
|
if ( is_wp_error( $secrets ) ) { |
467
|
|
|
Jetpack::delete_secrets( $action, $state ); |
|
|
|
|
468
|
|
|
return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user ); |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) { |
472
|
|
|
Jetpack::delete_secrets( $action, $state ); |
|
|
|
|
473
|
|
|
return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user ); |
474
|
|
|
} |
475
|
|
|
|
476
|
|
|
if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) { |
477
|
|
|
Jetpack::delete_secrets( $action, $state ); |
|
|
|
|
478
|
|
|
return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user ); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
Jetpack::delete_secrets( $action, $state ); |
|
|
|
|
482
|
|
|
|
483
|
|
|
if ( 'authorize' === $action ) { |
484
|
|
|
$this->tracking->record_user_event( 'jpc_verify_authorize_success', array(), $user ); |
485
|
|
|
} |
486
|
|
|
if ( 'publicize' === $action ) { |
487
|
|
|
$this->tracking->record_user_event( 'jpc_verify_publicize_success', array(), $user ); |
488
|
|
|
} |
489
|
|
|
if ( 'register' === $action ) { |
490
|
|
|
$this->tracking->record_user_event( 'jpc_verify_register_success', array(), $user ); |
491
|
|
|
} |
492
|
|
|
|
493
|
|
|
return $secrets['secret_2']; |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Wrapper for wp_authenticate( $username, $password ); |
498
|
|
|
* |
499
|
|
|
* @return WP_User|bool |
500
|
|
|
*/ |
501
|
|
|
function login() { |
502
|
|
|
Jetpack::init()->require_jetpack_authentication(); |
503
|
|
|
$user = wp_authenticate( 'username', 'password' ); |
504
|
|
|
if ( is_wp_error( $user ) ) { |
505
|
|
|
if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything. |
506
|
|
|
$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); |
507
|
|
|
} else { |
508
|
|
|
$this->error = $user; |
509
|
|
|
} |
510
|
|
|
return false; |
511
|
|
|
} else if ( !$user ) { // Shouldn't happen. |
512
|
|
|
$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 ); |
513
|
|
|
return false; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
return $user; |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* Returns the current error as an IXR_Error |
521
|
|
|
* |
522
|
|
|
* @return bool|IXR_Error |
523
|
|
|
*/ |
524
|
|
|
function error( $error = null, $tracks_event_name = null, $user = null ) { |
525
|
|
|
// record using Tracks |
526
|
|
|
if ( null !== $tracks_event_name ) { |
527
|
|
|
$this->tracks_record_error( $tracks_event_name, $error, $user ); |
528
|
|
|
} |
529
|
|
|
|
530
|
|
|
if ( !is_null( $error ) ) { |
531
|
|
|
$this->error = $error; |
532
|
|
|
} |
533
|
|
|
|
534
|
|
|
if ( is_wp_error( $this->error ) ) { |
535
|
|
|
$code = $this->error->get_error_data(); |
536
|
|
|
if ( !$code ) { |
537
|
|
|
$code = -10520; |
538
|
|
|
} |
539
|
|
|
$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() ); |
540
|
|
|
return new IXR_Error( $code, $message ); |
541
|
|
|
} else if ( is_a( $this->error, 'IXR_Error' ) ) { |
542
|
|
|
return $this->error; |
543
|
|
|
} |
544
|
|
|
|
545
|
|
|
return false; |
546
|
|
|
} |
547
|
|
|
|
548
|
|
|
/* API Methods */ |
549
|
|
|
|
550
|
|
|
/** |
551
|
|
|
* Just authenticates with the given Jetpack credentials. |
552
|
|
|
* |
553
|
|
|
* @return string The current Jetpack version number |
554
|
|
|
*/ |
555
|
|
|
function test_connection() { |
556
|
|
|
return JETPACK__VERSION; |
557
|
|
|
} |
558
|
|
|
|
559
|
|
|
function test_api_user_code( $args ) { |
560
|
|
|
$client_id = (int) $args[0]; |
561
|
|
|
$user_id = (int) $args[1]; |
562
|
|
|
$nonce = (string) $args[2]; |
563
|
|
|
$verify = (string) $args[3]; |
564
|
|
|
|
565
|
|
|
if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) { |
566
|
|
|
return false; |
567
|
|
|
} |
568
|
|
|
|
569
|
|
|
$user = get_user_by( 'id', $user_id ); |
570
|
|
|
if ( !$user || is_wp_error( $user ) ) { |
571
|
|
|
return false; |
572
|
|
|
} |
573
|
|
|
|
574
|
|
|
/* debugging |
575
|
|
|
error_log( "CLIENT: $client_id" ); |
576
|
|
|
error_log( "USER: $user_id" ); |
577
|
|
|
error_log( "NONCE: $nonce" ); |
578
|
|
|
error_log( "VERIFY: $verify" ); |
579
|
|
|
*/ |
580
|
|
|
|
581
|
|
|
$jetpack_token = Jetpack_Data::get_access_token( $user_id ); |
|
|
|
|
582
|
|
|
|
583
|
|
|
$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true ); |
584
|
|
|
if ( !$api_user_code ) { |
585
|
|
|
return false; |
586
|
|
|
} |
587
|
|
|
|
588
|
|
|
$hmac = hash_hmac( 'md5', json_encode( (object) array( |
589
|
|
|
'client_id' => (int) $client_id, |
590
|
|
|
'user_id' => (int) $user_id, |
591
|
|
|
'nonce' => (string) $nonce, |
592
|
|
|
'code' => (string) $api_user_code, |
593
|
|
|
) ), $jetpack_token->secret ); |
594
|
|
|
|
595
|
|
|
if ( ! hash_equals( $hmac, $verify ) ) { |
596
|
|
|
return false; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
return $user_id; |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* Disconnect this blog from the connected wordpress.com account |
604
|
|
|
* @return boolean |
605
|
|
|
*/ |
606
|
|
|
function disconnect_blog() { |
607
|
|
|
|
608
|
|
|
// For tracking |
609
|
|
|
if ( ! empty( $this->user->ID ) ) { |
610
|
|
|
wp_set_current_user( $this->user->ID ); |
611
|
|
|
} |
612
|
|
|
|
613
|
|
|
Jetpack::log( 'disconnect' ); |
614
|
|
|
Jetpack::disconnect(); |
615
|
|
|
|
616
|
|
|
return true; |
617
|
|
|
} |
618
|
|
|
|
619
|
|
|
/** |
620
|
|
|
* Unlink a user from WordPress.com |
621
|
|
|
* |
622
|
|
|
* This will fail if called by the Master User. |
623
|
|
|
*/ |
624
|
|
|
function unlink_user() { |
625
|
|
|
Jetpack::log( 'unlink' ); |
626
|
|
|
return Jetpack::unlink_user(); |
627
|
|
|
} |
628
|
|
|
|
629
|
|
|
/** |
630
|
|
|
* Returns any object that is able to be synced |
631
|
|
|
*/ |
632
|
|
|
function sync_object( $args ) { |
633
|
|
|
// e.g. posts, post, 5 |
634
|
|
|
list( $module_name, $object_type, $id ) = $args; |
635
|
|
|
|
636
|
|
|
$sync_module = Jetpack_Sync_Modules::get_module( $module_name ); |
637
|
|
|
$codec = Jetpack_Sync_Sender::get_instance()->get_codec(); |
638
|
|
|
|
639
|
|
|
return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) ); |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
/** |
643
|
|
|
* Returns the home URL and site URL for the current site which can be used on the WPCOM side for |
644
|
|
|
* IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM |
645
|
|
|
* and the remote Jetpack site. |
646
|
|
|
* |
647
|
|
|
* @return array |
648
|
|
|
*/ |
649
|
|
|
function validate_urls_for_idc_mitigation() { |
650
|
|
|
return array( |
651
|
|
|
'home' => Jetpack_Sync_Functions::home_url(), |
652
|
|
|
'siteurl' => Jetpack_Sync_Functions::site_url(), |
653
|
|
|
); |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
/** |
657
|
|
|
* Returns what features are available. Uses the slug of the module files. |
658
|
|
|
* |
659
|
|
|
* @return array |
660
|
|
|
*/ |
661
|
|
View Code Duplication |
function features_available() { |
662
|
|
|
$raw_modules = Jetpack::get_available_modules(); |
663
|
|
|
$modules = array(); |
664
|
|
|
foreach ( $raw_modules as $module ) { |
665
|
|
|
$modules[] = Jetpack::get_module_slug( $module ); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
return $modules; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Returns what features are enabled. Uses the slug of the modules files. |
673
|
|
|
* |
674
|
|
|
* @return array |
675
|
|
|
*/ |
676
|
|
View Code Duplication |
function features_enabled() { |
677
|
|
|
$raw_modules = Jetpack::get_active_modules(); |
678
|
|
|
$modules = array(); |
679
|
|
|
foreach ( $raw_modules as $module ) { |
680
|
|
|
$modules[] = Jetpack::get_module_slug( $module ); |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
return $modules; |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
function update_attachment_parent( $args ) { |
687
|
|
|
$attachment_id = (int) $args[0]; |
688
|
|
|
$parent_id = (int) $args[1]; |
689
|
|
|
|
690
|
|
|
return wp_update_post( array( |
691
|
|
|
'ID' => $attachment_id, |
692
|
|
|
'post_parent' => $parent_id, |
693
|
|
|
) ); |
694
|
|
|
} |
695
|
|
|
|
696
|
|
|
function json_api( $args = array() ) { |
697
|
|
|
$json_api_args = $args[0]; |
698
|
|
|
$verify_api_user_args = $args[1]; |
699
|
|
|
|
700
|
|
|
$method = (string) $json_api_args[0]; |
701
|
|
|
$url = (string) $json_api_args[1]; |
702
|
|
|
$post_body = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2]; |
703
|
|
|
$user_details = (array) $json_api_args[4]; |
704
|
|
|
$locale = (string) $json_api_args[5]; |
705
|
|
|
|
706
|
|
|
if ( !$verify_api_user_args ) { |
707
|
|
|
$user_id = 0; |
708
|
|
|
} elseif ( 'internal' === $verify_api_user_args[0] ) { |
709
|
|
|
$user_id = (int) $verify_api_user_args[1]; |
710
|
|
|
if ( $user_id ) { |
711
|
|
|
$user = get_user_by( 'id', $user_id ); |
712
|
|
|
if ( !$user || is_wp_error( $user ) ) { |
713
|
|
|
return false; |
714
|
|
|
} |
715
|
|
|
} |
716
|
|
|
} else { |
717
|
|
|
$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args ); |
718
|
|
|
if ( !$user_id ) { |
719
|
|
|
return false; |
720
|
|
|
} |
721
|
|
|
} |
722
|
|
|
|
723
|
|
|
/* debugging |
724
|
|
|
error_log( "-- begin json api via jetpack debugging -- " ); |
725
|
|
|
error_log( "METHOD: $method" ); |
726
|
|
|
error_log( "URL: $url" ); |
727
|
|
|
error_log( "POST BODY: $post_body" ); |
728
|
|
|
error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) ); |
729
|
|
|
error_log( "VERIFIED USER_ID: " . (int) $user_id ); |
730
|
|
|
error_log( "-- end json api via jetpack debugging -- " ); |
731
|
|
|
*/ |
732
|
|
|
|
733
|
|
|
if ( 'en' !== $locale ) { |
734
|
|
|
// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them. |
735
|
|
|
$new_locale = $locale; |
736
|
|
|
if ( strpos( $locale, '-' ) !== false ) { |
737
|
|
|
$locale_pieces = explode( '-', $locale ); |
738
|
|
|
$new_locale = $locale_pieces[0]; |
739
|
|
|
$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : ''; |
740
|
|
|
} else { |
741
|
|
|
// .com might pass 'fr' because thats what our language files are named as, where core seems |
742
|
|
|
// to do fr_FR - so try that if we don't think we can load the file. |
743
|
|
|
if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) { |
744
|
|
|
$new_locale = $locale . '_' . strtoupper( $locale ); |
745
|
|
|
} |
746
|
|
|
} |
747
|
|
|
|
748
|
|
|
if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) { |
749
|
|
|
unload_textdomain( 'default' ); |
750
|
|
|
load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' ); |
751
|
|
|
} |
752
|
|
|
} |
753
|
|
|
|
754
|
|
|
$old_user = wp_get_current_user(); |
755
|
|
|
wp_set_current_user( $user_id ); |
756
|
|
|
|
757
|
|
|
if ( $user_id ) { |
758
|
|
|
$token_key = false; |
759
|
|
|
} else { |
760
|
|
|
$jetpack = Jetpack::init(); |
761
|
|
|
$verified = $jetpack->verify_xml_rpc_signature(); |
762
|
|
|
$token_key = $verified['token_key']; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
$token = Jetpack_Data::get_access_token( $user_id, $token_key ); |
|
|
|
|
766
|
|
|
if ( !$token || is_wp_error( $token ) ) { |
767
|
|
|
return false; |
768
|
|
|
} |
769
|
|
|
|
770
|
|
|
define( 'REST_API_REQUEST', true ); |
771
|
|
|
define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' ); |
772
|
|
|
|
773
|
|
|
// needed? |
774
|
|
|
require_once ABSPATH . 'wp-admin/includes/admin.php'; |
775
|
|
|
|
776
|
|
|
require_once JETPACK__PLUGIN_DIR . 'class.json-api.php'; |
777
|
|
|
$api = WPCOM_JSON_API::init( $method, $url, $post_body ); |
778
|
|
|
$api->token_details['user'] = $user_details; |
779
|
|
|
require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php'; |
780
|
|
|
|
781
|
|
|
$display_errors = ini_set( 'display_errors', 0 ); |
782
|
|
|
ob_start(); |
783
|
|
|
$content_type = $api->serve( false ); |
|
|
|
|
784
|
|
|
$output = ob_get_clean(); |
785
|
|
|
ini_set( 'display_errors', $display_errors ); |
786
|
|
|
|
787
|
|
|
$nonce = wp_generate_password( 10, false ); |
788
|
|
|
$hmac = hash_hmac( 'md5', $nonce . $output, $token->secret ); |
789
|
|
|
|
790
|
|
|
wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 ); |
791
|
|
|
|
792
|
|
|
return array( |
793
|
|
|
(string) $output, |
794
|
|
|
(string) $nonce, |
795
|
|
|
(string) $hmac, |
796
|
|
|
); |
797
|
|
|
} |
798
|
|
|
|
799
|
|
|
/** |
800
|
|
|
* Handles authorization actions after connecting a site, such as enabling modules. |
801
|
|
|
* |
802
|
|
|
* This do_post_authorization() is used in this class, as opposed to calling |
803
|
|
|
* Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary. |
804
|
|
|
* |
805
|
|
|
* @return void |
806
|
|
|
*/ |
807
|
|
|
public function do_post_authorization() { |
808
|
|
|
/** This filter is documented in class.jetpack-cli.php */ |
809
|
|
|
$enable_sso = apply_filters( 'jetpack_start_enable_sso', true ); |
810
|
|
|
Jetpack::handle_post_authorization_actions( $enable_sso, false, false ); |
811
|
|
|
} |
812
|
|
|
} |
813
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.