Completed
Push — add/xmlrpc-user-api ( 457a7a...d35894 )
by
unknown
538:26 queued 531:29
created

Jetpack_XMLRPC_Server::disconnect_blog()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Tracking;
4
/**
5
 * Just a sack of functions.  Not actually an IXR_Server
6
 */
7
class Jetpack_XMLRPC_Server {
8
	/**
9
	 * The current error object
10
	 */
11
	public $error = null;
12
13
	/**
14
	 * The current user
15
	 */
16
	public $user = null;
17
18
	/**
19
	 * Whitelist of the XML-RPC methods available to the Jetpack Server. If the
20
	 * user is not authenticated (->login()) then the methods are never added,
21
	 * so they will get a "does not exist" error.
22
	 */
23
	function xmlrpc_methods( $core_methods ) {
24
		$jetpack_methods = array(
25
			'jetpack.jsonAPI'           => array( $this, 'json_api' ),
26
			'jetpack.verifyAction'      => array( $this, 'verify_action' ),
27
			'jetpack.getUser'           => array( $this, 'get_user' ),
28
			'jetpack.remoteRegister'    => array( $this, 'remote_register' ),
29
			'jetpack.remoteProvision'   => array( $this, 'remote_provision' ),
30
		);
31
32
		$this->user = $this->login();
33
34
		if ( $this->user ) {
35
			$jetpack_methods = array_merge( $jetpack_methods, array(
36
				'jetpack.testConnection'    => array( $this, 'test_connection' ),
37
				'jetpack.testAPIUserCode'   => array( $this, 'test_api_user_code' ),
38
				'jetpack.featuresAvailable' => array( $this, 'features_available' ),
39
				'jetpack.featuresEnabled'   => array( $this, 'features_enabled' ),
40
				'jetpack.disconnectBlog'    => array( $this, 'disconnect_blog' ),
41
				'jetpack.unlinkUser'        => array( $this, 'unlink_user' ),
42
				'jetpack.syncObject'        => array( $this, 'sync_object' ),
43
				'jetpack.idcUrlValidation'  => array( $this, 'validate_urls_for_idc_mitigation' ),
44
			) );
45
46
			if ( isset( $core_methods['metaWeblog.editPost'] ) ) {
47
				$jetpack_methods['metaWeblog.newMediaObject'] = $core_methods['metaWeblog.newMediaObject'];
48
				$jetpack_methods['jetpack.updateAttachmentParent'] = array( $this, 'update_attachment_parent' );
49
			}
50
51
			/**
52
			 * Filters the XML-RPC methods available to Jetpack for authenticated users.
53
			 *
54
			 * @since 1.1.0
55
			 *
56
			 * @param array $jetpack_methods XML-RPC methods available to the Jetpack Server.
57
			 * @param array $core_methods Available core XML-RPC methods.
58
			 * @param WP_User $user Information about a given WordPress user.
59
			 */
60
			$jetpack_methods = apply_filters( 'jetpack_xmlrpc_methods', $jetpack_methods, $core_methods, $this->user );
61
		}
62
63
		/**
64
		 * Filters the XML-RPC methods available to Jetpack for unauthenticated users.
65
		 *
66
		 * @since 3.0.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
		 */
71
		return apply_filters( 'jetpack_xmlrpc_unauthenticated_methods', $jetpack_methods, $core_methods );
72
	}
73
74
	/**
75
	 * Whitelist of the bootstrap XML-RPC methods
76
	 */
77
	function bootstrap_xmlrpc_methods() {
78
		return array(
79
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
80
			'jetpack.remoteRegister' => array( $this, 'remote_register' ),
81
		);
82
	}
83
84
	function authorize_xmlrpc_methods() {
85
		return array(
86
			'jetpack.remoteAuthorize' => array( $this, 'remote_authorize' ),
87
		);
88
	}
89
90
	function provision_xmlrpc_methods() {
91
		return array(
92
			'jetpack.remoteRegister'  => array( $this, 'remote_register' ),
93
			'jetpack.remoteProvision' => array( $this, 'remote_provision' ),
94
			'jetpack.remoteConnect'   => array( $this, 'remote_connect' ),
95
		);
96
	}
97
98
	/**
99
	 * This is used to verify whether a local user exists and what role they have,
100
	 * for the purposes of verifying that they are still a valid master user.
101
	 * @param local_user a local user ID, username or email address
102
	 * @return id the user's ID
103
	 * @return role the user's role
104
	 * @return login the user's login, aka username
105
	 * @return email the user's email address
106
	 * @return caps the user's capabilities
107
	 * @return allcaps the user's granular capabilities, merged from role capabilities
108
	 */
109
	function get_user( $request ) {
110
		$user = $this->fetch_and_verify_local_user( $request, 'jpc_get_user_fail' );
111
112
		if ( is_wp_error( $user ) ) {
113
			return $user;
114
		}
115
116
		if ( empty( $user ) ) {
117
			return $this->error(
118
				new Jetpack_Error(
119
					'user_unknown',
120
					__( 'User not found.', 'jetpack' ),
121
					404
122
				),
123
				'jpc_get_user_fail'
124
			);
125
		}
126
127
		return array(
128
			'id' => $user->ID,
129
			'login' => $user->user_login,
130
			'email' => $user->user_email,
131
			'roles' => $user->roles,
132
			'caps' => $user->caps,
133
			'allcaps' => $user->allcaps,
134
		);
135
	}
136
137
	function remote_authorize( $request ) {
138
		$user = get_user_by( 'id', $request['state'] );
139
		Tracking::record_user_event( 'jpc_remote_authorize_begin', array(), $user );
140
141
		foreach( array( 'secret', 'state', 'redirect_uri', 'code' ) as $required ) {
142
			if ( ! isset( $request[ $required ] ) || empty( $request[ $required ] ) ) {
143
				return $this->error( new Jetpack_Error( 'missing_parameter', 'One or more parameters is missing from the request.', 400 ), 'jpc_remote_authorize_fail' );
144
			}
145
		}
146
147
		if ( ! $user ) {
148
			return $this->error( new Jetpack_Error( 'user_unknown', 'User not found.', 404 ), 'jpc_remote_authorize_fail' );
149
		}
150
151
		if ( Jetpack::is_active() && Jetpack::is_user_connected( $request['state'] ) ) {
152
			return $this->error( new Jetpack_Error( 'already_connected', 'User already connected.', 400 ), 'jpc_remote_authorize_fail' );
153
		}
154
155
		$verified = $this->verify_action( array( 'authorize', $request['secret'], $request['state'] ) );
156
157
		if ( is_a( $verified, 'IXR_Error' ) ) {
158
			return $this->error( $verified, 'jpc_remote_authorize_fail' );
159
		}
160
161
		wp_set_current_user( $request['state'] );
162
163
		$client_server = new Jetpack_Client_Server;
164
		$result = $client_server->authorize( $request );
165
166
		if ( is_wp_error( $result ) ) {
167
			return $this->error( $result, 'jpc_remote_authorize_fail' );
168
		}
169
170
		Tracking::record_user_event( 'jpc_remote_authorize_success' );
171
172
		return array(
173
			'result' => $result,
174
		);
175
	}
176
177
	/**
178
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
179
	 * register this site so that a plan can be provisioned.
180
	 *
181
	 * @param array $request An array containing at minimum nonce and local_user keys.
182
	 *
183
	 * @return WP_Error|array
184
	 */
185
	public function remote_register( $request ) {
186
		Tracking::record_user_event( 'jpc_remote_register_begin', array() );
187
188
		$user = $this->fetch_and_verify_local_user( $request );
189
190
		if ( ! $user ) {
191
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_register_fail' );
192
		}
193
194
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
195
			return $this->error( $user, 'jpc_remote_register_fail' );
196
		}
197
198
		if ( empty( $request['nonce'] ) ) {
199
			return $this->error(
200
				new Jetpack_Error(
201
					'nonce_missing',
202
					__( 'The required "nonce" parameter is missing.', 'jetpack' ),
203
					400
204
				),
205
				'jpc_remote_register_fail'
206
			);
207
		}
208
209
		$nonce = sanitize_text_field( $request['nonce'] );
210
		unset( $request['nonce'] );
211
212
		$api_url  = Jetpack::fix_url_for_bad_hosts( Jetpack::api_url( 'partner_provision_nonce_check' ) );
213
		$response = Jetpack_Client::_wp_remote_request(
214
			esc_url_raw( add_query_arg( 'nonce', $nonce, $api_url ) ),
215
			array( 'method' => 'GET' ),
216
			true
217
		);
218
219
		if (
220
			200 !== wp_remote_retrieve_response_code( $response ) ||
221
			'OK' !== trim( wp_remote_retrieve_body( $response ) )
222
		) {
223
			return $this->error(
224
				new Jetpack_Error(
225
					'invalid_nonce',
226
					__( 'There was an issue validating this request.', 'jetpack' ),
227
					400
228
				),
229
				'jpc_remote_register_fail'
230
			);
231
		}
232
233
		if ( ! Jetpack_Options::get_option( 'id' ) || ! Jetpack_Data::get_access_token() || ! empty( $request['force'] ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
234
			wp_set_current_user( $user->ID );
235
236
			// This code mostly copied from Jetpack::admin_page_load.
237
			Jetpack::maybe_set_version_option();
238
			$registered = Jetpack::try_registration();
239
			if ( is_wp_error( $registered ) ) {
240
				return $this->error( $registered, 'jpc_remote_register_fail' );
241
			} elseif ( ! $registered ) {
242
				return $this->error(
243
					new Jetpack_Error(
244
						'registration_error',
245
						__( 'There was an unspecified error registering the site', 'jetpack' ),
246
						400
247
					),
248
					'jpc_remote_register_fail'
249
				);
250
			}
251
		}
252
253
		Tracking::record_user_event( 'jpc_remote_register_success' );
254
255
		return array(
256
			'client_id' => Jetpack_Options::get_option( 'id' )
257
		);
258
	}
259
260
	/**
261
	 * This XML-RPC method is called from the /jpphp/provision endpoint on WPCOM in order to
262
	 * register this site so that a plan can be provisioned.
263
	 *
264
	 * @param array $request An array containing at minimum a nonce key and a local_username key.
265
	 *
266
	 * @return WP_Error|array
267
	 */
268
	public function remote_provision( $request ) {
269
		$user = $this->fetch_and_verify_local_user( $request );
270
271
		if ( ! $user ) {
272
			return $this->error( new WP_Error( 'input_error', __( 'Valid user is required', 'jetpack' ), 400 ), 'jpc_remote_provision_fail' );
273
		}
274
275
		if ( is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
276
			return $this->error( $user, 'jpc_remote_provision_fail' );
277
		}
278
279
		$site_icon = get_site_icon_url();
280
281
		$auto_enable_sso = ( ! Jetpack::is_active() || Jetpack::is_module_active( 'sso' ) );
282
283
		/** This filter is documented in class.jetpack-cli.php */
284 View Code Duplication
		if ( apply_filters( 'jetpack_start_enable_sso', $auto_enable_sso ) ) {
285
			$redirect_uri = add_query_arg(
286
				array(
287
					'action'      => 'jetpack-sso',
288
					'redirect_to' => rawurlencode( admin_url() ),
289
				),
290
				wp_login_url() // TODO: come back to Jetpack dashboard?
291
			);
292
		} else {
293
			$redirect_uri = admin_url();
294
		}
295
296
		// Generate secrets.
297
		$role    = Jetpack::translate_user_to_role( $user );
298
		$secrets = Jetpack::init()->generate_secrets( 'authorize', $user->ID );
299
300
		$response = array(
301
			'jp_version'   => JETPACK__VERSION,
302
			'redirect_uri' => $redirect_uri,
303
			'user_id'      => $user->ID,
304
			'user_email'   => $user->user_email,
305
			'user_login'   => $user->user_login,
306
			'scope'        => Jetpack::sign_role( $role, $user->ID ),
307
			'secret'       => $secrets['secret_1'],
308
			'is_active'    => Jetpack::is_active(),
309
		);
310
311
		if ( $site_icon ) {
312
			$response['site_icon'] = $site_icon;
313
		}
314
315
		if ( ! empty( $request['onboarding'] ) ) {
316
			Jetpack::create_onboarding_token();
317
			$response['onboarding_token'] = Jetpack_Options::get_option( 'onboarding' );
318
		}
319
320
		return $response;
321
	}
322
323
	/**
324
	 * Given an array containing a local user identifier and a nonce, will attempt to fetch and set
325
	 * an access token for the given user.
326
	 *
327
	 * @param array $request An array containing local_user and nonce keys at minimum.
328
	 * @return mixed
329
	 */
330
	public function remote_connect( $request, $ixr_client = false ) {
331
		if ( Jetpack::is_active() ) {
332
			return $this->error(
333
				new WP_Error(
334
					'already_connected',
335
					__( 'Jetpack is already connected.', 'jetpack' ),
336
					400
337
				),
338
				'jpc_remote_connect_fail'
339
			);
340
		}
341
342
		$user = $this->fetch_and_verify_local_user( $request );
343
344
		if ( ! $user || is_wp_error( $user ) || is_a( $user, 'IXR_Error' ) ) {
345
			return $this->error(
346
				new WP_Error(
347
					'input_error',
348
					__( 'Valid user is required.', 'jetpack' ),
349
					400
350
				),
351
				'jpc_remote_connect_fail'
352
			);
353
		}
354
355
		if ( empty( $request['nonce'] ) ) {
356
			return $this->error(
357
				new WP_Error(
358
					'input_error',
359
					__( 'A non-empty nonce must be supplied.', 'jetpack' ),
360
					400
361
				),
362
				'jpc_remote_connect_fail'
363
			);
364
		}
365
366
		if ( ! $ixr_client ) {
367
			Jetpack::load_xml_rpc_client();
368
			$ixr_client = new Jetpack_IXR_Client();
369
		}
370
		$ixr_client->query( 'jetpack.getUserAccessToken', array(
0 ignored issues
show
Bug introduced by
It seems like $ixr_client is not always an object, but can also be of type boolean. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
371
			'nonce'            => sanitize_text_field( $request['nonce'] ),
372
			'external_user_id' => $user->ID,
373
		) );
374
375
		$token = $ixr_client->isError() ? false : $ixr_client->getResponse();
376
		if ( empty( $token ) ) {
377
			return $this->error(
378
				new WP_Error(
379
					'token_fetch_failed',
380
					__( 'Failed to fetch user token from WordPress.com.', 'jetpack' ),
381
					400
382
				),
383
				'jpc_remote_connect_fail'
384
			);
385
		}
386
		$token = sanitize_text_field( $token );
387
388
		Jetpack::update_user_token( $user->ID, sprintf( '%s.%d', $token, $user->ID ), true );
389
390
		$this->do_post_authorization();
391
392
		return Jetpack::is_active();
393
	}
394
395
	private function fetch_and_verify_local_user( $request, $error_slug = 'jpc_remote_provision_fail' ) {
396
		if ( empty( $request['local_user'] ) ) {
397
			return $this->error(
398
				new Jetpack_Error(
399
					'local_user_missing',
400
					__( 'The required "local_user" parameter is missing.', 'jetpack' ),
401
					400
402
				),
403
				$error_slug
404
			);
405
		}
406
407
		// local user is used to look up by login, email or ID
408
		$local_user_info = $request['local_user'];
409
410
		$user = get_user_by( 'login', $local_user_info );
411
412
		if ( ! $user ) {
413
			$user = get_user_by( 'email', $local_user_info );
414
		}
415
416
		if ( ! $user ) {
417
			$user = get_user_by( 'ID', $local_user_info );
418
		}
419
420
		return $user;
421
	}
422
423
	private function tracks_record_error( $name, $error, $user = null ) {
424
		if ( is_wp_error( $error ) ) {
425
			Tracking::record_user_event( $name, array(
426
				'error_code' => $error->get_error_code(),
427
				'error_message' => $error->get_error_message()
428
			), $user );
429
		} elseif( is_a( $error, 'IXR_Error' ) ) {
430
			Tracking::record_user_event( $name, array(
431
				'error_code' => $error->code,
432
				'error_message' => $error->message
433
			), $user );
434
		}
435
436
		return $error;
437
	}
438
439
	/**
440
	 * @return WP_Error|string secret_2 on success, WP_Error( error_code => error_code, error_message => error description, error_data => status code ) on failure
441
	 *
442
	 * Possible error_codes:
443
	 *
444
	 * verify_secret_1_missing
445
	 * verify_secret_1_malformed
446
	 * verify_secrets_missing: verification secrets are not found in database
447
	 * verify_secrets_incomplete: verification secrets are only partially found in database
448
	 * verify_secrets_expired: verification secrets have expired
449
	 * verify_secrets_mismatch: stored secret_1 does not match secret_1 sent by Jetpack.WordPress.com
450
	 * state_missing: required parameter of state not found
451
	 * state_malformed: state is not a digit
452
	 * invalid_state: state in request does not match the stored state
453
	 *
454
	 * The 'authorize' and 'register' actions have additional error codes
455
	 *
456
	 * Possible values for action are `authorize`, `publicize` and `register`.
457
	 *
458
	 * state_missing: a state ( user id ) was not supplied
459
	 * state_malformed: state is not the correct data type
460
	 * invalid_state: supplied state does not match the stored state
461
	 */
462
	function verify_action( $params ) {
463
		$action = $params[0];
464
		$verify_secret = $params[1];
465
		$state = isset( $params[2] ) ? $params[2] : '';
466
		$user = get_user_by( 'id', $state );
467
		$tracks_failure_event_name = '';
468
469
		if ( 'authorize' === $action ) {
470
			$tracks_failure_event_name = 'jpc_verify_authorize_fail';
471
			Tracking::record_user_event( 'jpc_verify_authorize_begin', array(), $user );
472
		}
473
		if ( 'publicize' === $action ) {
474
			// This action is used on a response from a direct XML-RPC done from WordPress.com
475
			$tracks_failure_event_name = 'jpc_verify_publicize_fail';
476
			Tracking::record_user_event( 'jpc_verify_publicize_begin', array(), $user );
477
		}
478
		if ( 'register' === $action ) {
479
			$tracks_failure_event_name = 'jpc_verify_register_fail';
480
			Tracking::record_user_event( 'jpc_verify_register_begin', array(), $user );
481
		}
482
483
		if ( empty( $verify_secret ) ) {
484
			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 );
485
		} else if ( ! is_string( $verify_secret ) ) {
486
			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 );
487
		} else if ( empty( $state ) ) {
488
			return $this->error( new Jetpack_Error( 'state_missing', sprintf( 'The required "%s" parameter is missing.', 'state' ), 400 ), $tracks_failure_event_name, $user );
489
		} else if ( ! ctype_digit( $state ) ) {
490
			return $this->error( new Jetpack_Error( 'state_malformed', sprintf( 'The required "%s" parameter is malformed.', 'state' ), 400 ), $tracks_failure_event_name, $user );
491
		}
492
493
		$secrets = Jetpack::get_secrets( $action, $state );
494
495
		if ( ! $secrets ) {
496
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
497
			return $this->error( new Jetpack_Error( 'verify_secrets_missing', 'Verification secrets not found', 400 ), $tracks_failure_event_name, $user );
498
		}
499
500
		if ( is_wp_error( $secrets ) ) {
501
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
502
			return $this->error( new Jetpack_Error( $secrets->get_error_code(), $secrets->get_error_message(), 400 ), $tracks_failure_event_name, $user );
503
		}
504
505
		if ( empty( $secrets['secret_1'] ) || empty( $secrets['secret_2'] ) || empty( $secrets['exp'] ) ) {
506
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
507
			return $this->error( new Jetpack_Error( 'verify_secrets_incomplete', 'Verification secrets are incomplete', 400 ), $tracks_failure_event_name, $user );
508
		}
509
510
		if ( ! hash_equals( $verify_secret, $secrets['secret_1'] ) ) {
511
			Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
512
			return $this->error( new Jetpack_Error( 'verify_secrets_mismatch', 'Secret mismatch', 400 ), $tracks_failure_event_name, $user );
513
		}
514
515
		Jetpack::delete_secrets( $action, $state );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack::delete_secrets() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
516
517
		if ( 'authorize' === $action ) {
518
			Tracking::record_user_event( 'jpc_verify_authorize_success', array(), $user );
519
		}
520
		if ( 'publicize' === $action ) {
521
			Tracking::record_user_event( 'jpc_verify_publicize_success', array(), $user );
522
		}
523
		if ( 'register' === $action ) {
524
			Tracking::record_user_event( 'jpc_verify_register_success', array(), $user );
525
		}
526
527
		return $secrets['secret_2'];
528
	}
529
530
	/**
531
	 * Wrapper for wp_authenticate( $username, $password );
532
	 *
533
	 * @return WP_User|bool
534
	 */
535
	function login() {
536
		Jetpack::init()->require_jetpack_authentication();
537
		$user = wp_authenticate( 'username', 'password' );
538
		if ( is_wp_error( $user ) ) {
539
			if ( 'authentication_failed' == $user->get_error_code() ) { // Generic error could mean most anything.
540
				$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
541
			} else {
542
				$this->error = $user;
543
			}
544
			return false;
545
		} else if ( !$user ) { // Shouldn't happen.
546
			$this->error = new Jetpack_Error( 'invalid_request', 'Invalid Request', 403 );
547
			return false;
548
		}
549
550
		return $user;
551
	}
552
553
	/**
554
	 * Returns the current error as an IXR_Error
555
	 *
556
	 * @return bool|IXR_Error
557
	 */
558
	function error( $error = null, $tracks_event_name = null, $user = null ) {
559
		// record using Tracks
560
		if ( null !== $tracks_event_name ) {
561
			$this->tracks_record_error( $tracks_event_name, $error, $user );
562
		}
563
564
		if ( !is_null( $error ) ) {
565
			$this->error = $error;
566
		}
567
568
		if ( is_wp_error( $this->error ) ) {
569
			$code = $this->error->get_error_data();
570
			if ( !$code ) {
571
				$code = -10520;
572
			}
573
			$message = sprintf( 'Jetpack: [%s] %s', $this->error->get_error_code(), $this->error->get_error_message() );
574
			return new IXR_Error( $code, $message );
575
		} else if ( is_a( $this->error, 'IXR_Error' ) ) {
576
			return $this->error;
577
		}
578
579
		return false;
580
	}
581
582
/* API Methods */
583
584
	/**
585
	 * Just authenticates with the given Jetpack credentials.
586
	 *
587
	 * @return string The current Jetpack version number
588
	 */
589
	function test_connection() {
590
		return JETPACK__VERSION;
591
	}
592
593
	function test_api_user_code( $args ) {
594
		$client_id = (int) $args[0];
595
		$user_id   = (int) $args[1];
596
		$nonce     = (string) $args[2];
597
		$verify    = (string) $args[3];
598
599
		if ( !$client_id || !$user_id || !strlen( $nonce ) || 32 !== strlen( $verify ) ) {
600
			return false;
601
		}
602
603
		$user = get_user_by( 'id', $user_id );
604
		if ( !$user || is_wp_error( $user ) ) {
605
			return false;
606
		}
607
608
		/* debugging
609
		error_log( "CLIENT: $client_id" );
610
		error_log( "USER:   $user_id" );
611
		error_log( "NONCE:  $nonce" );
612
		error_log( "VERIFY: $verify" );
613
		*/
614
615
		$jetpack_token = Jetpack_Data::get_access_token( $user_id );
0 ignored issues
show
Documentation introduced by
$user_id is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
616
617
		$api_user_code = get_user_meta( $user_id, "jetpack_json_api_$client_id", true );
618
		if ( !$api_user_code ) {
619
			return false;
620
		}
621
622
		$hmac = hash_hmac( 'md5', json_encode( (object) array(
623
			'client_id' => (int) $client_id,
624
			'user_id'   => (int) $user_id,
625
			'nonce'     => (string) $nonce,
626
			'code'      => (string) $api_user_code,
627
		) ), $jetpack_token->secret );
628
629
		if ( ! hash_equals( $hmac, $verify ) ) {
630
			return false;
631
		}
632
633
		return $user_id;
634
	}
635
636
	/**
637
	* Disconnect this blog from the connected wordpress.com account
638
	* @return boolean
639
	*/
640
	function disconnect_blog() {
641
642
		// For tracking
643
		if ( ! empty( $this->user->ID ) ) {
644
			wp_set_current_user( $this->user->ID );
645
		}
646
647
		Jetpack::log( 'disconnect' );
648
		Jetpack::disconnect();
649
650
		return true;
651
	}
652
653
	/**
654
	 * Unlink a user from WordPress.com
655
	 *
656
	 * This will fail if called by the Master User.
657
	 */
658
	function unlink_user() {
659
		Jetpack::log( 'unlink' );
660
		return Jetpack::unlink_user();
661
	}
662
663
	/**
664
	 * Returns any object that is able to be synced
665
	 */
666
	function sync_object( $args ) {
667
		// e.g. posts, post, 5
668
		list( $module_name, $object_type, $id ) = $args;
669
670
		$sync_module = Jetpack_Sync_Modules::get_module( $module_name );
671
		$codec = Jetpack_Sync_Sender::get_instance()->get_codec();
672
673
		return $codec->encode( $sync_module->get_object_by_id( $object_type, $id ) );
674
	}
675
676
	/**
677
	 * Returns the home URL and site URL for the current site which can be used on the WPCOM side for
678
	 * IDC mitigation to decide whether sync should be allowed if the home and siteurl values differ between WPCOM
679
	 * and the remote Jetpack site.
680
	 *
681
	 * @return array
682
	 */
683
	function validate_urls_for_idc_mitigation() {
684
		return array(
685
			'home'    => Jetpack_Sync_Functions::home_url(),
686
			'siteurl' => Jetpack_Sync_Functions::site_url(),
687
		);
688
	}
689
690
	/**
691
	 * Returns what features are available. Uses the slug of the module files.
692
	 *
693
	 * @return array
694
	 */
695 View Code Duplication
	function features_available() {
696
		$raw_modules = Jetpack::get_available_modules();
697
		$modules = array();
698
		foreach ( $raw_modules as $module ) {
699
			$modules[] = Jetpack::get_module_slug( $module );
700
		}
701
702
		return $modules;
703
	}
704
705
	/**
706
	 * Returns what features are enabled. Uses the slug of the modules files.
707
	 *
708
	 * @return array
709
	 */
710 View Code Duplication
	function features_enabled() {
711
		$raw_modules = Jetpack::get_active_modules();
712
		$modules = array();
713
		foreach ( $raw_modules as $module ) {
714
			$modules[] = Jetpack::get_module_slug( $module );
715
		}
716
717
		return $modules;
718
	}
719
720
	function update_attachment_parent( $args ) {
721
		$attachment_id = (int) $args[0];
722
		$parent_id     = (int) $args[1];
723
724
		return wp_update_post( array(
725
			'ID'          => $attachment_id,
726
			'post_parent' => $parent_id,
727
		) );
728
	}
729
730
	function json_api( $args = array() ) {
731
		$json_api_args = $args[0];
732
		$verify_api_user_args = $args[1];
733
734
		$method       = (string) $json_api_args[0];
735
		$url          = (string) $json_api_args[1];
736
		$post_body    = is_null( $json_api_args[2] ) ? null : (string) $json_api_args[2];
737
		$user_details = (array) $json_api_args[4];
738
		$locale       = (string) $json_api_args[5];
739
740
		if ( !$verify_api_user_args ) {
741
			$user_id = 0;
742
		} elseif ( 'internal' === $verify_api_user_args[0] ) {
743
			$user_id = (int) $verify_api_user_args[1];
744
			if ( $user_id ) {
745
				$user = get_user_by( 'id', $user_id );
746
				if ( !$user || is_wp_error( $user ) ) {
747
					return false;
748
				}
749
			}
750
		} else {
751
			$user_id = call_user_func( array( $this, 'test_api_user_code' ), $verify_api_user_args );
752
			if ( !$user_id ) {
753
				return false;
754
			}
755
		}
756
757
		/* debugging
758
		error_log( "-- begin json api via jetpack debugging -- " );
759
		error_log( "METHOD: $method" );
760
		error_log( "URL: $url" );
761
		error_log( "POST BODY: $post_body" );
762
		error_log( "VERIFY_ARGS: " . print_r( $verify_api_user_args, 1 ) );
763
		error_log( "VERIFIED USER_ID: " . (int) $user_id );
764
		error_log( "-- end json api via jetpack debugging -- " );
765
		*/
766
767
		if ( 'en' !== $locale ) {
768
			// .org mo files are named slightly different from .com, and all we have is this the locale -- try to guess them.
769
			$new_locale = $locale;
770
			if ( strpos( $locale, '-' ) !== false ) {
771
				$locale_pieces = explode( '-', $locale );
772
				$new_locale = $locale_pieces[0];
773
				$new_locale .= ( ! empty( $locale_pieces[1] ) ) ? '_' . strtoupper( $locale_pieces[1] ) : '';
774
			} else {
775
				// .com might pass 'fr' because thats what our language files are named as, where core seems
776
				// to do fr_FR - so try that if we don't think we can load the file.
777
				if ( ! file_exists( WP_LANG_DIR . '/' . $locale . '.mo' ) ) {
778
					$new_locale =  $locale . '_' . strtoupper( $locale );
779
				}
780
			}
781
782
			if ( file_exists( WP_LANG_DIR . '/' . $new_locale . '.mo' ) ) {
783
				unload_textdomain( 'default' );
784
				load_textdomain( 'default', WP_LANG_DIR . '/' . $new_locale . '.mo' );
785
			}
786
		}
787
788
		$old_user = wp_get_current_user();
789
		wp_set_current_user( $user_id );
790
791
		if ( $user_id ) {
792
			$token_key = false;
793
		} else {
794
			$jetpack   = Jetpack::init();
795
			$verified  = $jetpack->verify_xml_rpc_signature();
796
			$token_key = $verified['token_key'];
797
		}
798
799
		$token = Jetpack_Data::get_access_token( $user_id, $token_key );
0 ignored issues
show
Deprecated Code introduced by
The method Jetpack_Data::get_access_token() has been deprecated with message: 7.5 Use Connection_Manager instead.

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...
800
		if ( !$token || is_wp_error( $token ) ) {
801
			return false;
802
		}
803
804
		define( 'REST_API_REQUEST', true );
805
		define( 'WPCOM_JSON_API__BASE', 'public-api.wordpress.com/rest/v1' );
806
807
		// needed?
808
		require_once ABSPATH . 'wp-admin/includes/admin.php';
809
810
		require_once JETPACK__PLUGIN_DIR . 'class.json-api.php';
811
		$api = WPCOM_JSON_API::init( $method, $url, $post_body );
812
		$api->token_details['user'] = $user_details;
813
		require_once JETPACK__PLUGIN_DIR . 'class.json-api-endpoints.php';
814
815
		$display_errors = ini_set( 'display_errors', 0 );
816
		ob_start();
817
		$content_type = $api->serve( false );
0 ignored issues
show
Unused Code introduced by
$content_type is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
818
		$output = ob_get_clean();
819
		ini_set( 'display_errors', $display_errors );
820
821
		$nonce = wp_generate_password( 10, false );
822
		$hmac  = hash_hmac( 'md5', $nonce . $output, $token->secret );
823
824
		wp_set_current_user( isset( $old_user->ID ) ? $old_user->ID : 0 );
825
826
		return array(
827
			(string) $output,
828
			(string) $nonce,
829
			(string) $hmac,
830
		);
831
	}
832
833
	/**
834
	 * Handles authorization actions after connecting a site, such as enabling modules.
835
	 *
836
	 * This do_post_authorization() is used in this class, as opposed to calling
837
	 * Jetpack::handle_post_authorization_actions() directly so that we can mock this method as necessary.
838
	 *
839
	 * @return void
840
	 */
841
	public function do_post_authorization() {
842
		/** This filter is documented in class.jetpack-cli.php */
843
		$enable_sso = apply_filters( 'jetpack_start_enable_sso', true );
844
		Jetpack::handle_post_authorization_actions( $enable_sso, false, false );
845
	}
846
}
847