Completed
Push — fix/sync-phpcs-modules ( 266f99...4a4f68 )
by
unknown
25:25 queued 18:53
created

class.jetpack-xmlrpc-server.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 View Code Duplication
		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 ) );
0 ignored issues
show
The method get_object_by_id cannot be called on $sync_module (of type boolean|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
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 View Code Duplication
	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 View Code Duplication
	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