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