Completed
Push — add/user-authentication ( 34ed8f...93b97f )
by
unknown
288:07 queued 279:09
created

connection/legacy/class.jetpack-xmlrpc-server.php (4 issues)

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
use Automattic\Jetpack\Tracking;
5
6
/**
7
 * Just a sack of functions.  Not actually an IXR_Server
8
 */
9
class Jetpack_XMLRPC_Server {
10
	/**
11
	 * The current error object
12
	 */
13
	public $error = null;
14
15
	/**
16
	 * The current user
17
	 */
18
	public $user = null;
19
20
	private $tracking;
21
22
	/**
23
	 * Creates a new XMLRPC server object.
24
	 *
25
	 * @param Automattic\Jetpack\Connection\Manager $manager the connection manager object.
0 ignored issues
show
Should the type for parameter $manager not be Connection_Manager|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
26
	 */
27
	function __construct( $manager = null ) {
28
		$this->tracking = new Tracking( 'jetpack', $manager );
0 ignored issues
show
It seems like $manager defined by parameter $manager on line 27 can also be of type object<Automattic\Jetpack\Connection\Manager>; however, Automattic\Jetpack\Tracking::__construct() does only seem to accept object<Automattic\Jetpac...onnection\Manager>|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
29
	}
30
31
	/**
32
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
33
	 * user is not authenticated (->login()) then the methods are never added,
34
	 * so they will get a "does not exist" error.
35
	 */
36
	function xmlrpc_methods( $core_methods ) {
37
		$jetpack_methods = array(
38
			'jetpack.jsonAPI'           => array( $this, 'json_api' ),
39
			'jetpack.verifyAction'      => array( $this, 'verify_action' ),
40
			'jetpack.getUser'           => array( $this, 'get_user' ),
41
			'jetpack.remoteRegister'    => array( $this, 'remote_register' ),
42
			'jetpack.remoteProvision'   => array( $this, 'remote_provision' ),
43
		);
44
45
		$this->user = $this->login();
46
47
		if ( $this->user ) {
48
			$jetpack_methods = array_merge( $jetpack_methods, array(
49
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
50
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
51
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
52
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
53
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
54
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
55
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
56
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
57
			) );
58
59
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
60
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
61
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
62
			}
63
64
			/**
65
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
66
			 *
67
			 * @since 1.1.0
68
			 *
69
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
70
			 * @param array $core_methods Available core XML-RPC methods.
71
			 * @param WP_User $user Information about a given WordPress user.
72
			 */
73
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
74
		}
75
76
		/**
77
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
78
		 *
79
		 * @since 3.0.0
80
		 *
81
		 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
82
		 * @param array $core_methods Available core XML-RPC methods.
83
		 */
84
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
85
	}
86
87
	/**
88
	 * Whitelist of the bootstrap XML-RPC methods
89
	 */
90
	function bootstrap_xmlrpc_methods() {
91
		return array(
92
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
93
			'jetpack.remoteRegister' => array( $this, 'remote_register' ),
94
		);
95
	}
96
97
	function authorize_xmlrpc_methods() {
98
		return array(
99
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
100
		);
101
	}
102
103
	function provision_xmlrpc_methods() {
104
		return array(
105
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
106
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
107
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
108
			'jetpack.getUser'         => array( $this, 'get_user' ),
109
		);
110
	}
111
112
	/**
113
	 * Used to verify whether a local user exists and what role they have.
114
	 *
115
	 * @param int|string|array $request One of:
116
	 *                         int|string The local User's ID, username, or email address.
117
	 *                         array      A request array containing:
118
	 *                                    0: int|string The local User's ID, username, or email address.
119
	 *
120
	 * @return array|IXR_Error Information about the user, or error if no such user found:
121
	 *                         roles:     string[] The user's rols.
122
	 *                         login:     string   The user's username.
123
	 *                         email_hash string[] The MD5 hash of the user's normalized email address.
124
	 *                         caps       string[] The user's capabilities.
125
	 *                         allcaps    string[] The user's granular capabilities, merged from role capabilities.
126
	 *                         token_key  string   The Token Key of the user's Jetpack token. Empty string if none.
127
	 */
128
	function get_user( $request ) {
129
		$user_id = is_array( $request ) ? $request[0] : $request;
130
131
		if ( ! $user_id ) {
132
			return $this->error(
133
				new Jetpack_Error(
134
					'invalid_user',
135
					__( 'Invalid user identifier.', 'jetpack' ),
136
					400
137
				),
138
				'jpc_get_user_fail'
139
			);
140
		}
141
142
		$user = $this->get_user_by_anything( $user_id );
143
144
		if ( ! $user ) {
145
			return $this->error(
146
				new Jetpack_Error(
147
					'user_unknown',
148
					__( 'User not found.', 'jetpack' ),
149
					404
150
				),
151
				'jpc_get_user_fail'
152
			);
153
		}
154
155
		$connection = Jetpack::connection();
156
		$user_token = $connection->get_access_token( $user->ID );
157
158
		if ( $user_token ) {
159
			list( $user_token_key, $user_token_private ) = explode( '.', $user_token->secret );
160
			if ( $user_token_key === $user_token->secret ) {
161
				$user_token_key = '';
162
			}
163
		} else {
164
			$user_token_key = '';
165
		}
166
167
		return array(
168
			'id'         => $user->ID,
169
			'login'      => $user->user_login,
170
			'email_hash' => md5( strtolower( trim( $user->user_email ) ) ),
171
			'roles'      => $user->roles,
172
			'caps'       => $user->caps,
173
			'allcaps'    => $user->allcaps,
174
			'token_key'  => $user_token_key,
175
		);
176
	}
177
178
	function remote_authorize( $request ) {
179
		$user = get_user_by( 'id', $request['state'] );
180
		$this->tracking->record_user_event( 'jpc_remote_authorize_begin', array(), $user );
181
182
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
183
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
184
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
185
			}
186
		}
187
188
		if ( ! $user ) {
189
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
190
		}
191
192
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
193
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
194
		}
195
196
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
197
198
		if ( is_a( $verified, 'IXR_Error' ) ) {
199
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
200
		}
201
202
		wp_set_current_user( $request['state'] );
203
204
		$client_server = new Jetpack_Client_Server;
205
		$result = $client_server->authorize( $request );
206
207
		if ( is_wp_error( $result ) ) {
208
			return $this->error( $result, 'jpc_remote_authorize_fail' );
209
		}
210
211
		$this->tracking->record_user_event( 'jpc_remote_authorize_success' );
212
213
		return array(
214
			'result' => $result,
215
		);
216
	}
217
218
	/**
219
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
220
	 * register this site so that a plan can be provisioned.
221
	 *
222
	 * @param array $request An array containing at minimum nonce and local_user keys.
223
	 *
224
	 * @return WP_Error|array
225
	 */
226
	public function remote_register( $request ) {
227
		$this->tracking->record_user_event( 'jpc_remote_register_begin', array() );
228
229
		$user = $this->fetch_and_verify_local_user( $request );
230
231
		if ( ! $user ) {
232
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
233
		}
234
235
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
236
			return $this->error( $user, 'jpc_remote_register_fail' );
237
		}
238
239
		if ( empty( $request['nonce'] ) ) {
240
			return $this->error(
241
				new Jetpack_Error(
242
					'nonce_missing',
243
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
244
					400
245
				),
246
				'jpc_remote_register_fail'
247
			);
248
		}
249
250
		$nonce = sanitize_text_field( $request['nonce'] );
251
		unset( $request['nonce'] );
252
253
		$api_url  = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
254
		$response = Client::_wp_remote_request(
255
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
256
			array( 'method' => 'GET' ),
257
			true
258
		);
259
260
		if (
261
			200 !== wp_remote_retrieve_response_code( $response ) ||
262
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
263
		) {
264
			return $this->error(
265
				new Jetpack_Error(
266
					'invalid_nonce',
267
					__( 'There was an issue validating this request.', 'jetpack' ),
268
					400
269
				),
270
				'jpc_remote_register_fail'
271
			);
272
		}
273
274
		if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Data::get_access_token() || ! empty( $request['force'] ) ) {
275
			wp_set_current_user( $user->ID );
276
277
			// This code mostly copied from Jetpack::admin_page_load.
278
			Jetpack::maybe_set_version_option();
279
			$registered = Jetpack::try_registration();
280
			if ( is_wp_error( $registered ) ) {
281
				return $this->error( $registered, 'jpc_remote_register_fail' );
282
			} elseif ( ! $registered ) {
283
				return $this->error(
284
					new Jetpack_Error(
285
						'registration_error',
286
						__( 'There was an unspecified error registering the site', 'jetpack' ),
287
						400
288
					),
289
					'jpc_remote_register_fail'
290
				);
291
			}
292
		}
293
294
		$this->tracking->record_user_event( 'jpc_remote_register_success' );
295
296
		return array(
297
			'client_id' => Jetpack_Options::get_option( 'id' )
298
		);
299
	}
300
301
	/**
302
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
303
	 * register this site so that a plan can be provisioned.
304
	 *
305
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
306
	 *
307
	 * @return WP_Error|array
308
	 */
309
	public function remote_provision( $request ) {
310
		$user = $this->fetch_and_verify_local_user( $request );
311
312
		if ( ! $user ) {
313
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
314
		}
315
316
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
317
			return $this->error( $user, 'jpc_remote_provision_fail' );
318
		}
319
320
		$site_icon = get_site_icon_url();
321
322
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
323
324
		/** This filter is documented in class.jetpack-cli.php */
325 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
326
			$redirect_uri = add_query_arg(
327
				array(
328
					'action'      => 'jetpack-sso',
329
					'redirect_to' => rawurlencode( admin_url() ),
330
				),
331
				wp_login_url() // TODO: come back to Jetpack dashboard?
332
			);
333
		} else {
334
			$redirect_uri = admin_url();
335
		}
336
337
		// Generate secrets.
338
		$role    = Jetpack::translate_user_to_role( $user );
339
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
340
341
		$response = array(
342
			'jp_version'   => JETPACK__VERSION,
343
			'redirect_uri' => $redirect_uri,
344
			'user_id'      => $user->ID,
345
			'user_email'   => $user->user_email,
346
			'user_login'   => $user->user_login,
347
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
348
			'secret'       => $secrets['secret_1'],
349
			'is_active'    => Jetpack::is_active(),
350
		);
351
352
		if ( $site_icon ) {
353
			$response['site_icon'] = $site_icon;
354
		}
355
356
		if ( ! empty( $request['onboarding'] ) ) {
357
			Jetpack::create_onboarding_token();
358
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
359
		}
360
361
		return $response;
362
	}
363
364
	/**
365
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
366
	 * an access token for the given user.
367
	 *
368
	 * @param array $request An array containing local_user and nonce keys at minimum.
369
	 * @return mixed
370
	 */
371
	public function remote_connect( $request, $ixr_client = false ) {
372
		if ( Jetpack::is_active() ) {
373
			return $this->error(
374
				new WP_Error(
375
					'already_connected',
376
					__( 'Jetpack is already connected.', 'jetpack' ),
377
					400
378
				),
379
				'jpc_remote_connect_fail'
380
			);
381
		}
382
383
		$user = $this->fetch_and_verify_local_user( $request );
384
385
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
386
			return $this->error(
387
				new WP_Error(
388
					'input_error',
389
					__( 'Valid user is required.', 'jetpack' ),
390
					400
391
				),
392
				'jpc_remote_connect_fail'
393
			);
394
		}
395
396
		if ( empty( $request['nonce'] ) ) {
397
			return $this->error(
398
				new WP_Error(
399
					'input_error',
400
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
401
					400
402
				),
403
				'jpc_remote_connect_fail'
404
			);
405
		}
406
407
		if ( ! $ixr_client ) {
408
			Jetpack::load_xml_rpc_client();
409
			$ixr_client = new Jetpack_IXR_Client();
410
		}
411
		$ixr_client->query( 'jetpack.getUserAccessToken', array(
412
			'nonce'            => sanitize_text_field( $request['nonce'] ),
413
			'external_user_id' => $user->ID,
414
		) );
415
416
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
417
		if ( empty( $token ) ) {
418
			return $this->error(
419
				new WP_Error(
420
					'token_fetch_failed',
421
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
422
					400
423
				),
424
				'jpc_remote_connect_fail'
425
			);
426
		}
427
		$token = sanitize_text_field( $token );
428
429
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
430
431
		$this->do_post_authorization();
432
433
		return Jetpack::is_active();
434
	}
435
436
	private function fetch_and_verify_local_user( $request ) {
437
		if ( empty( $request['local_user'] ) ) {
438
			return $this->error(
439
				new Jetpack_Error(
440
					'local_user_missing',
441
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
442
					400
443
				),
444
				'jpc_remote_provision_fail'
445
			);
446
		}
447
448
		// local user is used to look up by login, email or ID
449
		$local_user_info = $request['local_user'];
450
451
		return $this->get_user_by_anything( $local_user_info );
452
	}
453
454
	private function get_user_by_anything( $user_id ) {
455
		$user = get_user_by( 'login', $user_id );
456
457
		if ( ! $user ) {
458
			$user = get_user_by( 'email', $user_id );
459
		}
460
461
		if ( ! $user ) {
462
			$user = get_user_by( 'ID', $user_id );
463
		}
464
465
		return $user;
466
	}
467
468
	private function tracks_record_error( $name, $error, $user = null ) {
469
		if ( is_wp_error( $error ) ) {
470
			$this->tracking->record_user_event( $name, array(
471
				'error_code' => $error->get_error_code(),
472
				'error_message' => $error->get_error_message()
473
			), $user );
474
		} elseif( is_a( $error, 'IXR_Error' ) ) {
475
			$this->tracking->record_user_event( $name, array(
476
				'error_code' => $error->code,
477
				'error_message' => $error->message
478
			), $user );
479
		}
480
481
		return $error;
482
	}
483
484
	/**
485
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
486
	 *
487
	 * Possible error_codes:
488
	 *
489
	 * verify_secret_1_missing
490
	 * verify_secret_1_malformed
491
	 * verify_secrets_missing: verification secrets are not found in database
492
	 * verify_secrets_incomplete: verification secrets are only partially found in database
493
	 * verify_secrets_expired: verification secrets have expired
494
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
495
	 * state_missing: required parameter of state not found
496
	 * state_malformed: state is not a digit
497
	 * invalid_state: state in request does not match the stored state
498
	 *
499
	 * The 'authorize' and 'register' actions have additional error codes
500
	 *
501
	 * Possible values for action are `authorize`, `publicize` and `register`.
502
	 *
503
	 * state_missing: a state ( user id ) was not supplied
504
	 * state_malformed: state is not the correct data type
505
	 * invalid_state: supplied state does not match the stored state
506
	 */
507
	function verify_action( $params ) {
508
		$action = $params[0];
509
		$verify_secret = $params[1];
510
		$state = isset( $params[2] ) ? $params[2] : '';
511
		$user = get_user_by( 'id', $state );
512
		$tracks_failure_event_name = '';
513
514
		if ( 'authorize' === $action ) {
515
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
516
			$this->tracking->record_user_event( 'jpc_verify_authorize_begin', array(), $user );
517
		}
518
		if ( 'publicize' === $action ) {
519
			// This action is used on a response from a direct XML-RPC done from WordPress.com
520
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
521
			$this->tracking->record_user_event( 'jpc_verify_publicize_begin', array(), $user );
522
		}
523
		if ( 'register' === $action ) {
524
			$tracks_failure_event_name = 'jpc_verify_register_fail';
525
			$this->tracking->record_user_event( 'jpc_verify_register_begin', array(), $user );
526
		}
527
528
		if ( empty( $verify_secret ) ) {
529
			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 );
530
		} else if ( ! is_string( $verify_secret ) ) {
531
			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 );
532
		} else if ( empty( $state ) ) {
533
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
534
		} else if ( ! ctype_digit( $state ) ) {
535
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
536
		}
537
538
		$secrets = Jetpack::get_secrets( $action, $state );
539
540
		if ( ! $secrets ) {
541
			Jetpack::delete_secrets( $action, $state );
542
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
543
		}
544
545
		if ( is_wp_error( $secrets ) ) {
546
			Jetpack::delete_secrets( $action, $state );
547
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
548
		}
549
550
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
551
			Jetpack::delete_secrets( $action, $state );
552
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
553
		}
554
555
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
556
			Jetpack::delete_secrets( $action, $state );
557
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
558
		}
559
560
		Jetpack::delete_secrets( $action, $state );
561
562
		if ( 'authorize' === $action ) {
563
			$this->tracking->record_user_event( 'jpc_verify_authorize_success', array(), $user );
564
		}
565
		if ( 'publicize' === $action ) {
566
			$this->tracking->record_user_event( 'jpc_verify_publicize_success', array(), $user );
567
		}
568
		if ( 'register' === $action ) {
569
			$this->tracking->record_user_event( 'jpc_verify_register_success', array(), $user );
570
		}
571
572
		return $secrets['secret_2'];
573
	}
574
575
	/**
576
	 * Wrapper for wp_authenticate( $username, $password );
577
	 *
578
	 * @return WP_User|bool
579
	 */
580
	function login() {
581
		Jetpack::init()->require_jetpack_authentication();
0 ignored issues
show
The method require_jetpack_authentication() does not seem to exist on object<Jetpack>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
582
		$user = wp_authenticate( 'username', 'password' );
583
		if ( is_wp_error( $user ) ) {
584
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
585
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
586
			} else {
587
				$this->error = $user;
588
			}
589
			return false;
590
		} else if ( !$user ) { // Shouldn't happen.
591
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
592
			return false;
593
		}
594
595
		return $user;
596
	}
597
598
	/**
599
	 * Returns the current error as an IXR_Error
600
	 *
601
	 * @return bool|IXR_Error
602
	 */
603
	function error( $error = null, $tracks_event_name = null, $user = null ) {
604
		// record using Tracks
605
		if ( null !== $tracks_event_name ) {
606
			$this->tracks_record_error( $tracks_event_name, $error, $user );
607
		}
608
609
		if ( !is_null( $error ) ) {
610
			$this->error = $error;
611
		}
612
613
		if ( is_wp_error( $this->error ) ) {
614
			$code = $this->error->get_error_data();
615
			if ( !$code ) {
616
				$code = -10520;
617
			}
618
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
619
			return new IXR_Error( $code, $message );
620
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
621
			return $this->error;
622
		}
623
624
		return false;
625
	}
626
627
/* API Methods */
628
629
	/**
630
	 * Just authenticates with the given Jetpack credentials.
631
	 *
632
	 * @return string The current Jetpack version number
633
	 */
634
	function test_connection() {
635
		return JETPACK__VERSION;
636
	}
637
638
	function test_api_user_code( $args ) {
639
		$client_id = (int) $args[0];
640
		$user_id   = (int) $args[1];
641
		$nonce     = (string) $args[2];
642
		$verify    = (string) $args[3];
643
644
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
645
			return false;
646
		}
647
648
		$user = get_user_by( 'id', $user_id );
649
		if ( !$user || is_wp_error( $user ) ) {
650
			return false;
651
		}
652
653
		/* debugging
654
		error_log( "CLIENT: $client_id" );
655
		error_log( "USER:   $user_id" );
656
		error_log( "NONCE:  $nonce" );
657
		error_log( "VERIFY: $verify" );
658
		*/
659
660
		$jetpack_token = Jetpack_Data::get_access_token( $user_id );
661
662
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
663
		if ( !$api_user_code ) {
664
			return false;
665
		}
666
667
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
668
			'client_id' => (int) $client_id,
669
			'user_id'   => (int) $user_id,
670
			'nonce'     => (string) $nonce,
671
			'code'      => (string) $api_user_code,
672
		) ), $jetpack_token->secret );
673
674
		if ( ! hash_equals( $hmac, $verify ) ) {
675
			return false;
676
		}
677
678
		return $user_id;
679
	}
680
681
	/**
682
	* Disconnect this blog from the connected wordpress.com account
683
	* @return boolean
684
	*/
685
	function disconnect_blog() {
686
687
		// For tracking
688
		if ( ! empty( $this->user->ID ) ) {
689
			wp_set_current_user( $this->user->ID );
690
		}
691
692
		Jetpack::log( 'disconnect' );
693
		Jetpack::disconnect();
694
695
		return true;
696
	}
697
698
	/**
699
	 * Unlink a user from WordPress.com
700
	 *
701
	 * This will fail if called by the Master User.
702
	 */
703
	function unlink_user() {
704
		Jetpack::log( 'unlink' );
705
		return Jetpack::unlink_user();
706
	}
707
708
	/**
709
	 * Returns any object that is able to be synced
710
	 */
711
	function sync_object( $args ) {
712
		// e.g. posts, post, 5
713
		list( $module_name, $object_type, $id ) = $args;
714
715
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
716
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
717
718
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
719
	}
720
721
	/**
722
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
723
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
724
	 * and the remote Jetpack site.
725
	 *
726
	 * @return array
727
	 */
728
	function validate_urls_for_idc_mitigation() {
729
		return array(
730
			'home'    => Jetpack_Sync_Functions::home_url(),
731
			'siteurl' => Jetpack_Sync_Functions::site_url(),
732
		);
733
	}
734
735
	/**
736
	 * Returns what features are available. Uses the slug of the module files.
737
	 *
738
	 * @return array
739
	 */
740 View Code Duplication
	function features_available() {
741
		$raw_modules = Jetpack::get_available_modules();
742
		$modules = array();
743
		foreach ( $raw_modules as $module ) {
744
			$modules[] = Jetpack::get_module_slug( $module );
745
		}
746
747
		return $modules;
748
	}
749
750
	/**
751
	 * Returns what features are enabled. Uses the slug of the modules files.
752
	 *
753
	 * @return array
754
	 */
755 View Code Duplication
	function features_enabled() {
756
		$raw_modules = Jetpack::get_active_modules();
757
		$modules = array();
758
		foreach ( $raw_modules as $module ) {
759
			$modules[] = Jetpack::get_module_slug( $module );
760
		}
761
762
		return $modules;
763
	}
764
765
	function update_attachment_parent( $args ) {
766
		$attachment_id = (int) $args[0];
767
		$parent_id     = (int) $args[1];
768
769
		return wp_update_post( array(
770
			'ID'          => $attachment_id,
771
			'post_parent' => $parent_id,
772
		) );
773
	}
774
775
	function json_api( $args = array() ) {
776
		$json_api_args = $args[0];
777
		$verify_api_user_args = $args[1];
778
779
		$method       = (string) $json_api_args[0];
780
		$url          = (string) $json_api_args[1];
781
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
782
		$user_details = (array) $json_api_args[4];
783
		$locale       = (string) $json_api_args[5];
784
785
		if ( !$verify_api_user_args ) {
786
			$user_id = 0;
787
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
788
			$user_id = (int) $verify_api_user_args[1];
789
			if ( $user_id ) {
790
				$user = get_user_by( 'id', $user_id );
791
				if ( !$user || is_wp_error( $user ) ) {
792
					return false;
793
				}
794
			}
795
		} else {
796
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
797
			if ( !$user_id ) {
798
				return false;
799
			}
800
		}
801
802
		/* debugging
803
		error_log( "-- begin json api via jetpack debugging -- " );
804
		error_log( "METHOD: $method" );
805
		error_log( "URL: $url" );
806
		error_log( "POST BODY: $post_body" );
807
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
808
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
809
		error_log( "-- end json api via jetpack debugging -- " );
810
		*/
811
812
		if ( 'en' !== $locale ) {
813
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
814
			$new_locale = $locale;
815
			if ( strpos( $locale, '-' ) !== false ) {
816
				$locale_pieces = explode( '-', $locale );
817
				$new_locale = $locale_pieces[0];
818
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
819
			} else {
820
				// .com might pass 'fr' because thats what our language files are named as, where core seems
821
				// to do fr_FR - so try that if we don't think we can load the file.
822
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
823
					$new_locale =  $locale . '_' . strtoupper( $locale );
824
				}
825
			}
826
827
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
828
				unload_textdomain( 'default' );
829
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
830
			}
831
		}
832
833
		$old_user = wp_get_current_user();
834
		wp_set_current_user( $user_id );
835
836
		if ( $user_id ) {
837
			$token_key = false;
838
		} else {
839
			$jetpack   = Jetpack::init();
840
			$verified  = $jetpack->verify_xml_rpc_signature();
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::verify_xml_rpc_signature() has been deprecated with message: use Automattic\Jetpack\Connection\Manager::verify_xml_rpc_signature

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.

Loading history...
841
			$token_key = $verified['token_key'];
842
		}
843
844
		$token = Jetpack_Data::get_access_token( $user_id, $token_key );
845
		if ( !$token || is_wp_error( $token ) ) {
846
			return false;
847
		}
848
849
		define( 'REST_API_REQUEST', true );
850
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
851
852
		// needed?
853
		require_once ABSPATH . 'wp-admin/includes/admin.php';
854
855
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
856
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
857
		$api->token_details['user'] = $user_details;
858
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
859
860
		$display_errors = ini_set( 'display_errors', 0 );
861
		ob_start();
862
		$content_type = $api->serve( false );
863
		$output = ob_get_clean();
864
		ini_set( 'display_errors', $display_errors );
865
866
		$nonce = wp_generate_password( 10, false );
867
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
868
869
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
870
871
		return array(
872
			(string) $output,
873
			(string) $nonce,
874
			(string) $hmac,
875
		);
876
	}
877
878
	/**
879
	 * Handles authorization actions after connecting a site, such as enabling modules.
880
	 *
881
	 * This do_post_authorization() is used in this class, as opposed to calling
882
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
883
	 *
884
	 * @return void
885
	 */
886
	public function do_post_authorization() {
887
		/** This filter is documented in class.jetpack-cli.php */
888
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
889
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
890
	}
891
}
892