Completed
Push — add/signature-error-reporting ( 906206...21e253 )
by
unknown
104:48 queued 94:49
created

Jetpack_XMLRPC_Server::verify_registration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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